@suffolklitlab/alkiln 4.9.2 → 4.10.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.
@@ -42,8 +42,8 @@ jobs:
42
42
  - name: Set name of artifact folder with date and UTC time
43
43
  # https://www.shell-tips.com/linux/how-to-format-date-and-time-in-linux-macos-and-bash/#how-to-format-a-date-in-bash
44
44
  run: |
45
- echo "ARTIFACT_NAME=alkiln_tests_output-$(date +'%Y-%m-%d at %Hhrs %Mmins %Ssecs' -u) UTC" >> $GITHUB_ENV
46
- echo "UNIT_TESTS_ARTIFACT_NAME=_alkiln_tests_misc-$(date +'%Y-%m-%d at %Hhrs %Mmins %Ssecs' -u) UTC" >> $GITHUB_ENV
45
+ echo "ARTIFACT_NAME=alkiln-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u) UTC" >> $GITHUB_ENV
46
+ echo "UNIT_TESTS_ARTIFACT_NAME=_alkiln_tests_misc-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u) UTC" >> $GITHUB_ENV
47
47
  - name: Set languages
48
48
  run: echo "EXTRA_LANGUAGES=${{ github.event.inputs.extra_languages }}" >> $GITHUB_ENV
49
49
  if: ${{ github.event.inputs.extra_languages != '' }}
@@ -71,7 +71,7 @@ jobs:
71
71
  if: ${{ always() }}
72
72
  with:
73
73
  name: ${{ env.ARTIFACT_NAME }}
74
- path: ./alkiln_tests_output-*
74
+ path: ./alkiln-*
75
75
  # Delete Playground Project and project name file
76
76
  - name: Cleanup
77
77
  if: ${{ always() }}
package/CHANGELOG.md CHANGED
@@ -40,6 +40,25 @@ Format:
40
40
  -->
41
41
  <!-- ## [Unreleased] -->
42
42
 
43
+ ## [4.10.0] - 2022-11-17
44
+ ### Changed
45
+ - Don't mark `...there_in_another | False |` in story tables as invalid, as it's necessary for some
46
+ workflows (see https://github.com/SuffolkLITLab/ALKiln/pull/580 for a longer discussion).
47
+ - Explicitly **not** documented, as we don't want to encourage people to use it if it's not
48
+ necessary for their interviews.
49
+ - continuing between screens will no longer press button with the `btn-primary` class. This means
50
+ that it won't press "Exit", or "Restart" buttons, to avoid getting in an infinite loop.
51
+ - Shorten test report and artifact filenames, attempting to have fewer files whose whole paths are
52
+ longer than 260 characters. (See https://github.com/SuffolkLITLab/ALKiln/pull/626 for details).
53
+
54
+ ### Fixed
55
+ - Corrects the month in the artifact folder timestamp; was printing things like `81` for September
56
+ instead of `09`, because of a `+` being interpreted as Javascript string concatenation and not math.
57
+
58
+ ## [4.9.3] - 2022-09-07
59
+ ### Fixed
60
+ - `retry` of failed tests not implemented for developers. See https://github.com/SuffolkLITLab/ALKiln/issues/601.
61
+
43
62
  ## [4.9.2] - 2022-09-02
44
63
  ### Fixed
45
64
  - Environment variables `SERVER_RELOAD_TIMEOUT_SECONDS` and `MAX_SECONDS_FOR_SERVER_RELOAD` were being used as milliseconds instead of seconds. See https://github.com/SuffolkLITLab/ALKiln/issues/606.
package/action.yml CHANGED
@@ -43,7 +43,7 @@ runs:
43
43
  # Human-readable date/time:
44
44
  # https://www.shell-tips.com/linux/how-to-format-date-and-time-in-linux-macos-and-bash/#how-to-format-a-date-in-bash
45
45
  run: |
46
- echo "ARTIFACT_NAME=alkiln_tests_output $(date +'%Y-%m-%d at %Hhrs %Mmins %Ssecs' -u) UTC" >> $GITHUB_ENV
46
+ echo "ARTIFACT_NAME=alkiln-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u) UTC" >> $GITHUB_ENV
47
47
  if ${{ github.event.inputs.extra_languages != '' }}
48
48
  then
49
49
  echo "Manually set languages: ${{ github.event.inputs.extra_languages }}"
@@ -77,7 +77,7 @@ runs:
77
77
  "scripts": {
78
78
  "test": "npm run langs_setup && npm run cucumber -- $@",
79
79
  "cucumber_base": "./node_modules/.bin/cucumber-js --require ./node_modules/@suffolklitlab/alkiln/lib/index.js",
80
- "cucumber": "run_cucumber(){ npm run cucumber_base -- \"$@\" docassemble/*/data/sources/*.feature; }; run_cucumber",
80
+ "cucumber": "run_cucumber(){ npm run cucumber_base -- \"$@\" docassemble/*/data/sources/*.feature --retry 1; }; run_cucumber",
81
81
  "langs": "npm run langs_setup && ./node_modules/.bin/cucumber-js --require ./node_modules/.bin/alkiln-cucumber docassemble/*/data/sources/*_lang*.feature",
82
82
  "langs_setup": "node_modules/.bin/alkiln-langs 'docassemble/*/data/sources/*.feature'",
83
83
  "setup": "node_modules/.bin/alkiln-setup",
@@ -128,7 +128,7 @@ runs:
128
128
  uses: actions/upload-artifact@v2
129
129
  with:
130
130
  name: ${{ env.ARTIFACT_NAME }}
131
- path: ./alkiln_tests_output-*
131
+ path: ./alkiln-*
132
132
 
133
133
  # ##################################
134
134
  # # A developer's workflow should look similar to the below
package/lib/scope.js CHANGED
@@ -54,7 +54,33 @@ let is_secret_var = function(row) {
54
54
  return original.flags !== undefined && original.flags.secret_name !== undefined;
55
55
  }
56
56
 
57
+ let fromBase64 = function(base64_str) {
58
+ /* Takes a base64 encoded string and returns it decoded.
59
+ * Note that this can take strings with invalid base 64 characters,
60
+ * but will change those characters to `/`s instead.
61
+ */
62
+ let buffer = Buffer.from( base64_str, 'base64' );
63
+ return buffer.toString( `utf-8` );
64
+ }
65
+
66
+ let toBase64 = function ( utf8_str, exclude_padding=true) {
67
+ /* Encoded a given string (@param utf8_str) in base64.
68
+ * @param exclude_padding {boolean} - if true, strips off `=` characters
69
+ * that are added as padding while base64 encoding (docassemble doesn't
70
+ * include those characters, and uses the UTF-7 base64 encoding
71
+ * (RFC-2152)). For more details, see
72
+ * https://github.com/nodejs/node/issues/6107#issuecomment-207177131
73
+ */
74
+ if ( typeof( utf8_str ) !== `string` ) { // Prevent an error
75
+ return `Will+Never+Match+Existing+DOM+Node==`;
76
+ }
77
+ let buffer = Buffer.from( utf8_str, 'utf-8' );
78
+ let base64str = buffer.toString( `base64` )
79
+ return exclude_padding ? base64str.replace(/=/g, '') : base64str;
80
+ }
81
+
57
82
  let proxies_regex = /(\bx\.)|(\bx\[)|(\[[ijklmn]\])/g;
83
+ let da_base64_regex = /^[A-Za-z0-9+/]+$/;
58
84
  let misc_artifacts_dir = `_alkiln_test-misc_artifacts`;
59
85
 
60
86
  module.exports = {
@@ -620,7 +646,7 @@ module.exports = {
620
646
  * - Birthdate fields that will be showing up relatively soon
621
647
  * - Time fields?
622
648
  */
623
- let base64_var_name = await scope.toBase64( scope, { utf8_str: var_name });
649
+ let base64_var_name = toBase64( var_name );
624
650
 
625
651
  // Will catch some radio and checkbox inputs too, which will then be rejected
626
652
  // Note: I think `showif`s with `code` don't use `data-saveas`
@@ -659,12 +685,14 @@ module.exports = {
659
685
  return to_manipulate;
660
686
  }, // Ends scope.setVar()
661
687
 
688
+ continue_button_selector: `fieldset.da-field-buttons button[type="submit"].btn-primary`,
689
+ signature_selector: `fieldset .dasigsave`,
662
690
  continue: async function ( scope ) {
663
691
  /* Presses whatever button it finds that might lead to the next page. */
664
692
  // Any selectors I find seem somewhat precarious.
665
693
  let elem = await Promise.race([
666
- scope.page.waitForSelector(`fieldset.da-field-buttons button[type="submit"]`), // other pages (this is the most consistent way)
667
- scope.page.waitForSelector(`fieldset .dasigsave`), // signature page
694
+ scope.page.waitForSelector(scope.continue_button_selector), // other pages (this is the most consistent way)
695
+ scope.page.waitForSelector(scope.signature_selector), // signature page
668
696
  ]);
669
697
  await elem.evaluate( el => { return el.className });
670
698
  // Waits for navigation or user error
@@ -675,15 +703,16 @@ module.exports = {
675
703
  /** Return true if a button that will allow continuing appears
676
704
  * on this page. Otherwise return false. */
677
705
 
678
- // Possible to find this on an event screen with `buttons:`, like an exit button
706
+ // Will only find this with "Continue buttons":
707
+ // * An actual continue button that doesn't set a variable
708
+ // <button class="btn btn-da btn-primary" type="submit">Continue</button>
709
+ // * One that sets a variable:
710
+ // <button type="submit" class="btn btn-da btn-primary" name="Zm9v" value="True">Continue</button>
711
+ // It will not find event screen `buttons:`, like an exit button or a restart button
679
712
  // <button type="submit" class="btn btn-da btn-danger" name="X211bHRpcGxlX2Nob2ljZQ" value="0">exit</button>
680
- // An actual continue button that doesn't set a variable
681
- // <button class="btn btn-da btn-primary" type="submit">Continue</button>
682
- // One that sets a variable:
683
- // <button type="submit" class="btn btn-da btn-primary" name="Zm9v" value="True">Continue</button>
684
713
  // `buttons:` can be used in question blocks as choices
685
- let regular = await scope.page.$( `fieldset.da-field-buttons button[type="submit"]` );
686
- let signature = await scope.page.$( `fieldset .dasigsave` );
714
+ let regular = await scope.page.$(scope.continue_button_selector);
715
+ let signature = await scope.page.$(scope.signature_selector);
687
716
 
688
717
  return regular !== null || signature !== null;
689
718
 
@@ -972,9 +1001,9 @@ module.exports = {
972
1001
  if ( !encoded_trigger_var ) {
973
1002
  // TODO: Add documentation and then link to it in this message.
974
1003
  await scope.addToReport(scope, { type: `warning`, value: `You are missing an element containing the data for the page's trigger variable. Some fields, like loops with index variables, may not work correctly in these tests. See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#a-missing-trigger-variable.` });
975
- encoded_trigger_var = await scope.toBase64( scope, { utf8_str: `None` });
1004
+ encoded_trigger_var = toBase64( `None` );
976
1005
  }
977
- let trigger = await scope.fromBase64( scope, { base64_str: encoded_trigger_var });
1006
+ let trigger = fromBase64( encoded_trigger_var );
978
1007
 
979
1008
 
980
1009
  // Some `show if` fields have `names` like '_field_n' instead of their var name. Ex: _field_1
@@ -1120,7 +1149,18 @@ module.exports = {
1120
1149
  // Inputs like textareas don't have values
1121
1150
  if ( values.length > 0 ) {
1122
1151
  for ( let value of values ){
1123
- possible_table_rows.push({ var: var_name, value, });
1152
+ possible_table_rows.push({ var: var_name, value: value, });
1153
+ // Ignore blanks, and values that certainly aren't base64 encoded like True and False
1154
+ // Modified from https://stackoverflow.com/a/8571649/11416267
1155
+ // DA doesn't use `==` to pad base 64
1156
+ if (value !== 'None' && value !== 'True' && value !== 'False'
1157
+ && value.match(da_base64_regex)) {
1158
+ let value_decoded = fromBase64(value);
1159
+ let values_decoded = scope.decodeFully( scope, {expr_str: value_decoded });
1160
+ for (let possible_val of values_decoded) {
1161
+ possible_table_rows.push({var: var_name, value: possible_val, });
1162
+ }
1163
+ }
1124
1164
  }
1125
1165
  } else {
1126
1166
  possible_table_rows.push({ var: var_name, value: '', });
@@ -1145,11 +1185,11 @@ module.exports = {
1145
1185
 
1146
1186
  let field_like_names = {};
1147
1187
  let field_like_names_encoded = $('input[name="_varnames"]')[0].attribs.value;
1148
- let field_like_names_str = await scope.fromBase64( scope, { base64_str: field_like_names_encoded });
1188
+ let field_like_names_str = fromBase64( field_like_names_encoded );
1149
1189
  let field_like_names_json = JSON.parse( field_like_names_str );
1150
1190
  for ( let field_like_key in field_like_names_json ) {
1151
- let field_like_DOM_name = await scope.fromBase64( scope, { base64_str: field_like_key });
1152
- let field_like_var_name = await scope.fromBase64( scope, { base64_str: field_like_names_json[ field_like_key ]});
1191
+ let field_like_DOM_name = fromBase64( field_like_key );
1192
+ let field_like_var_name = fromBase64( field_like_names_json[ field_like_key ] );
1153
1193
  field_like_names[ field_like_DOM_name ] = field_like_var_name;
1154
1194
  }
1155
1195
 
@@ -1178,14 +1218,11 @@ module.exports = {
1178
1218
  * @param encoded {str} - base64 encoded string of the node
1179
1219
  * @param field_like_names {obj} - e.g. `{ _field_0: 'some_checkbox_var_name' }`
1180
1220
  * @param nota {bool} - is the field a 'none of the above' field? */
1181
- let var_names = [];
1182
- let var_name = '';
1183
-
1184
1221
  // E.g. continue buttons that don't set a variable
1185
- if ( encoded === undefined ) { return var_names; }
1222
+ if ( encoded === undefined ) { return []; }
1186
1223
 
1187
1224
  // Names can be encoded multiple times, so start peeling the layers
1188
- let layer_1_decoded = await scope.fromBase64( scope, { base64_str: encoded });
1225
+ let layer_1_decoded = fromBase64( encoded );
1189
1226
  // If this is of the format `_field_n`, we need to get the actual matching var name.
1190
1227
  if ( field_like_names[ layer_1_decoded ] ) {
1191
1228
  layer_1_decoded = field_like_names[ layer_1_decoded ];
@@ -1200,60 +1237,66 @@ module.exports = {
1200
1237
  // TODO: Should we move this back into `getAllFields()`?
1201
1238
  }
1202
1239
 
1203
- // A lot of checkbox fields have a key hidden inside them. (not yesno type checkboxes)
1204
- let checkbox_match = layer_1_decoded.match( /^(.*)\[[BR]'(.*)'\]/ );
1240
+ return scope.decodeFully(scope, { expr_str: layer_1_decoded, field_like_names, nota });
1241
+ }, // Ends scope.getPossibleVarNames()
1242
+
1243
+ decodeFully: function ( scope, { expr_str, field_like_names = {}, nota} ) {
1244
+ /*
1245
+ * Given a DA variable or value name, make sure it's fully decoded from base64.
1246
+ * @param variable_str {str} - a python variable string, potentially partially base64 encoded
1247
+ * Some examples:
1248
+ * - my_variable
1249
+ * - checkboxes_choices[B'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
1250
+ * - foo[bar][B'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
1251
+ * @param field_like_names {obj} - e.g. `{ _field_0: 'some_checkbox_var_name' }`
1252
+ * @param nota {bool} - is the field a 'none of the above' field?
1253
+ */
1254
+ // Some fields have a key hidden inside them.
1255
+ // This gets the variable name before the final brackets, and the likely
1256
+ // still base64 encoded string inside the brackets
1257
+ let before_and_in_brackets = expr_str.match( /^(.*)\[[BR]'(.*)'\]/ );
1205
1258
 
1206
1259
  // Of the format `foo[B'encoded']` or `foo[R'encoded']`
1260
+ // The B means internal to docassemble, the encoded portion is a string,
1261
+ // and the R means that the python `repr` function was called with a
1262
+ // non-string object
1207
1263
  // See https://github.com/jhpyle/docassemble/blob/595ad0a08572c0188f9ab49b1a6af99a3621cd42/docassemble_base/docassemble/base/standardformatter.py#L2108
1208
1264
 
1209
- // What about something like foo[R'encoded1'][B'encoded2']? Is that a
1210
- // thing? Can they be index numbers? Or just `foo['encoded1']`? What
1211
- // would their format be? The above? `foo[R'actual_choice'][B'encoded']`?
1212
- // `foo['something'][B'encoded']`?
1213
-
1214
1265
  // =====================
1215
1266
  // Ex: checkboxes_choices[B'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
1216
1267
  // Ex: checkboxes_choices[R'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
1217
- if ( checkbox_match ) {
1218
-
1219
- let key_or_name = checkbox_match[1];
1268
+ let possible = [];
1269
+ if ( before_and_in_brackets ) {
1270
+ let key_or_name = before_and_in_brackets[1];
1220
1271
  // If this is of the format `_field_n`, we need to get the actual matching var name.
1221
- if ( field_like_names[ key_or_name ] ) {
1222
- var_name = field_like_names[ key_or_name ];
1223
- } else {
1224
- var_name = key_or_name;
1225
- }
1272
+ let var_name = field_like_names [ key_or_name ] || key_or_name;
1226
1273
 
1227
- // Our own marker for a 'none of the above' checkbox choice
1228
- if ( nota ) { var_names.push( `${ var_name }['None']` ); }
1229
- else {
1230
- // Get the choice name. E.g. option_1, option_2
1231
- let box_layer_1_decoded = await scope.fromBase64( scope, { base64_str: checkbox_match[2] });
1232
- var_names.push( `${ var_name }['${ box_layer_1_decoded }']` );
1274
+ let dictionary_key = before_and_in_brackets[2];
1233
1275
 
1234
- try {
1235
- // The part inside `[B'encoded']` could need one more decoding (or more?)
1236
- // It may have invalid chars if it's not a one-word var name, though, like '_'
1237
- let box_layer_2_decoded = await scope.fromBase64( scope, { base64_str: box_layer_1_decoded });
1238
- var_names.push(`${ var_name }['${ box_layer_2_decoded }']`);
1239
- } catch ( err ) {} // It wasn't double encoded. Do nothing.
1276
+ // Our own marker for a 'none of the above' checkbox choice
1277
+ if ( nota ) {
1278
+ possible.push( `${ var_name }['None']` );
1279
+ } else {
1280
+ if (dictionary_key.match(da_base64_regex)) {
1281
+ // Get the choice name. E.g. option_1, option_2
1282
+ let dictionary_key_decoded = fromBase64( dictionary_key );
1283
+ possible.push( `${ var_name }['${ dictionary_key_decoded }']` );
1284
+ if (dictionary_key_decoded.match(da_base64_regex)) {
1285
+ try {
1286
+ // The part inside `[B'encoded']` could need one more decoding.
1287
+ possible.push(`${ var_name }['${ fromBase64( dictionary_key_decoded ) }']`);
1288
+ } catch ( err ) {} // It wasn't double encoded. Do nothing.
1289
+ }
1290
+ }
1240
1291
  }
1241
1292
 
1242
1293
  // =====================
1243
- // Otherwise, just the var name (non checkboxes or yesno checkbox type things)
1294
+ // If bracket regex didn't match, just use var name
1244
1295
  } else {
1245
- var_name = layer_1_decoded;
1246
- var_names.push(`${ layer_1_decoded }`);
1247
- // // Values are sometimes encoded multiple times (check this happens with a plain var name)
1248
- // let layer_2_decoded = await scope.fromBase64( layer_1_decoded );
1249
- // names.var_names.push( layer_2_decoded );
1296
+ possible.push(`${ expr_str }`);
1250
1297
  }
1251
-
1252
- // Detecting valid variable names/characters
1253
- // https://stackoverflow.com/a/23377268
1254
-
1255
- return var_names;
1256
- }, // Ends scope.getPossibleVarNames()
1298
+ return possible;
1299
+ },
1257
1300
 
1258
1301
  getOptionValues: async function ( scope, { $select }) {
1259
1302
  /* Returns all the option node values of the select node. */
@@ -1261,35 +1304,11 @@ module.exports = {
1261
1304
  let option_nodes = $select.find( `option` );
1262
1305
  for ( let opt of option_nodes ) {
1263
1306
  options.push( opt.attribs.value );
1264
- // Dropdown options created with objects will be encoded
1265
- let decoded = await scope.fromBase64( scope, { base64_str: opt.attribs.value });
1266
- options.push( decoded );
1307
+ // Dropdowns created w/ objects are base64 encoded; but it's handled in getPossibleTableRowMatches
1267
1308
  }
1268
- // TODO: These may be encoded? Just 'decode' and append those vals too.
1269
- // TODO: Keep those decoded values somewhere for setting the right
1270
- // option in the DOM. Not sure how that can be put together sensically.
1271
1309
  return options;
1272
1310
  }, // Ends scope.getOptionValues()
1273
1311
 
1274
- fromBase64: async function ( scope, { base64_str }) {
1275
- let buffer = Buffer.from( base64_str, 'base64' );
1276
- return buffer.toString( `utf-8` );
1277
- },
1278
-
1279
- toBase64: async function ( scope, { utf8_str }, exclude_padding=true) {
1280
- if ( typeof( utf8_str ) !== `string` ) { // Prevent an error
1281
- return `Will+Never+Match+Existing+DOM+Node==`;
1282
- }
1283
- // Sometimes, converting to base64 results in padding characters ('=')
1284
- // being added to the end. Docassemble does not use these padding
1285
- // characters everywhere, so we allow for stripping them using
1286
- // the exclude_padding argument. For more details, see
1287
- // https://github.com/nodejs/node/issues/6107#issuecomment-207177131
1288
- let buffer = Buffer.from( utf8_str, 'utf-8' );
1289
- let base64str = buffer.toString( `base64` )
1290
- return exclude_padding ? base64str.replace(/=/g, '') : base64str;
1291
- },
1292
-
1293
1312
 
1294
1313
  // TODO: Maybe add `from_story_table` to the var data table (array...)
1295
1314
  getMatchingRows: async function ( scope, { field, var_data }) {
@@ -1481,7 +1500,12 @@ module.exports = {
1481
1500
  await scope.addToReport(scope, { type: `warning`, value: multiple_matches_msg });
1482
1501
  }
1483
1502
 
1484
- if ( session_vars.get_debug() ) { console.log( `final_matching_rows:\n`, JSON.stringify( final_matching_rows )); }
1503
+ if ( session_vars.get_debug() ) {
1504
+ console.log( `final_matching_rows:\n`, JSON.stringify( final_matching_rows ));
1505
+ if ( final_matching_rows.length == 0) {
1506
+ console.log(` field:\n`, JSON.stringify( field ) );
1507
+ }
1508
+ }
1485
1509
  return final_matching_rows;
1486
1510
  }, // Ends scope.getMatchingRows()
1487
1511
 
@@ -1685,8 +1709,6 @@ module.exports = {
1685
1709
  },
1686
1710
  select: async function ( scope, { handle, answer }) {
1687
1711
  // Note: I don't remember what's really going on in here.
1688
- // I will note that values of `select` elements are strings,
1689
- // not variables, so we don't need to check for variable names.
1690
1712
 
1691
1713
  // A dropdown's option value can be one of two things
1692
1714
  // Try to find the element using the first value
@@ -1707,7 +1729,7 @@ module.exports = {
1707
1729
  // If that literal value isn't on the page, it should be a base64 encoded value
1708
1730
  // TODO: Can these be double encoded? Can they be encoded like objects?
1709
1731
  } else {
1710
- let base64_name = await scope.toBase64( scope, { utf8_str: answer });
1732
+ let base64_name = toBase64( answer );
1711
1733
  await handle.select( base64_name );
1712
1734
  }
1713
1735
 
@@ -2127,13 +2149,18 @@ module.exports = {
2127
2149
  enhanced_var_data.push( special_row );
2128
2150
 
2129
2151
  // Neutralize a `.there_is_another` row in a story table
2130
- } else if ( from_story_table && var_datum.var.match( /\.there_is_another$/ ) ) {
2152
+ } else if ( from_story_table && var_datum.var.match( /\.there_is_another$/ ) && var_datum.value !== `False`) {
2131
2153
  var_datum.invalid = true;
2132
2154
  // Removing the row will just cause a different error that will take longer to fail and
2133
2155
  // probably add a confusing message to the report, so mutating seems, sadly, more useful.
2134
2156
  var_datum.value = `False`; // The original value is still preserved
2135
2157
  // Warn the developer not to use `.there_is_another`.
2136
- await scope.addToReport( scope, { type: `warning`, value: `The attribute \`.there_is_another\` is invalid in story table tests. Replace it with \`.target_number\` in your \`var\` and \`trigger\` columns. Set the \`value\` to the number of items in that list. This test will now set this row's \`value\` to \`False\`. See https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing#there_is_another. The row data is\n${ JSON.stringify( var_datum.original )}` });
2158
+ await scope.addToReport( scope, { type: `warning`,
2159
+ value: 'The attribute `.there_is_another` is invalid in story table tests. Replace it with '
2160
+ + '`.target_number` in your `var` and `trigger` columns. Set the `value` to the number of items in '
2161
+ + `that list. This test will now set this row's \`value\` to \`False\`. See `
2162
+ + `https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing#there_is_another. `
2163
+ + `The row data is\n${ JSON.stringify( var_datum.original )}` });
2137
2164
  }
2138
2165
  }
2139
2166
 
@@ -2821,4 +2848,24 @@ module.exports = {
2821
2848
 
2822
2849
  return all_are_included;
2823
2850
  }, // Ends scope.reportIncludesAllExpected()
2851
+
2852
+ reportDoesNotInclude: async function ( scope, { not_expected=[] }) {
2853
+ /* Tests the behavior of this testing framework.
2854
+ * Test whether the expected values are not included in the test report.
2855
+ *
2856
+ * @param not_expected {array} Arr of strs that should not appear in the report.
2857
+ */
2858
+ let current_report_obj = scope.report.get( scope.scenario_id );
2859
+ let scenario = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
2860
+ let report = await scope.getPrintableScenario( scope, { scenario });
2861
+ let none_included = true;
2862
+ for ( let one_expectation of not_expected ) {
2863
+ if ( report.includes( one_expectation )) {
2864
+ none_included = false;
2865
+ expect( report ).not.to.contain( one_expectation );
2866
+ }
2867
+ }
2868
+
2869
+ return none_included;
2870
+ },
2824
2871
  };
package/lib/steps.js CHANGED
@@ -115,6 +115,7 @@ Before(async (scenario) => {
115
115
  scope.scenario_id = uuidv4();
116
116
  scope.expected_status = null;
117
117
  scope.expected_in_report = null;
118
+ scope.expected_not_in_report = null;
118
119
  scope.server_reload_promise = null;
119
120
 
120
121
  await scope.addReportHeading(scope, {scenario});
@@ -1068,6 +1069,19 @@ Given(/the Scenario report should include/i, async ( report_text ) => {
1068
1069
 
1069
1070
  });
1070
1071
 
1072
+ Given(/the Scenario report should not include/i, async ( report_text ) => {
1073
+ /* WARNING: If a Step will fail, this Step must come first.
1074
+ *
1075
+ * Prepare to test that the Scenario report does not include `report_text`.
1076
+ * The actual test has to be done at the end of the Scenario as some text
1077
+ * may be added to the report there. */
1078
+ if ( !scope.expected_not_in_report ) {
1079
+ scope.expected_not_in_report = [];
1080
+ }
1081
+ scope.expected_not_in_report.push( report_text );
1082
+
1083
+ });
1084
+
1071
1085
  Given(/the final Scenario status should be "(passed|failed)"/i, async ( expected_status ) => {
1072
1086
  /* WARNING: If a Step will fail, this Step must come first.
1073
1087
  *
@@ -1156,6 +1170,20 @@ After(async function(scenario) {
1156
1170
  else { scenario.result.status = `FAILED`; }
1157
1171
  }
1158
1172
 
1173
+ if (scope.expected_not_in_report && scope.expected_not_in_report.length > 0) {
1174
+ let not_expected = scope.expected_not_in_report;
1175
+ // Reset report values no matter what so they don't mess up future scenarios
1176
+ scope.expected_not_in_report = null;
1177
+ let all_were_not_there = await scope.reportDoesNotInclude( scope, { not_expected });
1178
+
1179
+ if ( all_were_not_there ) {
1180
+ scenario.result.status = `PASSED`;
1181
+ } else {
1182
+ scenario.result.status = `FAILED`;
1183
+ }
1184
+
1185
+ }
1186
+
1159
1187
  // If there is a page open, then sign out and close it
1160
1188
  if ( scope.page ) {
1161
1189
  // Catch a possible reload error.
@@ -11,14 +11,14 @@ files.readable_date = function() {
11
11
  */
12
12
  let date = new Date(); // now
13
13
  let day = ("0" + date.getUTCDate()).slice(-2);
14
- let month = ("0" + date.getUTCMonth() + 1).slice(-2);
14
+ let month = ("0" + (date.getUTCMonth() + 1)).slice(-2);
15
15
  let year = date.getUTCFullYear();
16
16
  let hours = ("0" + date.getUTCHours()).slice(-2);
17
17
  let mins = ("0" + date.getUTCMinutes()).slice(-2);
18
18
  let secs = ("0" + date.getUTCSeconds()).slice(-2);
19
19
  let msecs = ("00" + date.getUTCMilliseconds()).slice(-3);
20
20
 
21
- let fancy_str = `${ year }-${ month }-${ day } at ${ hours }hr ${ mins }min ${ secs }sec ${ msecs }ms UTC`;
21
+ let fancy_str = `${ year }-${ month }-${ day } at ${ hours }h${ mins }m${ secs }s${ msecs }ms UTC`;
22
22
  return fancy_str;
23
23
  }; // Ends files.readable_date()
24
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "4.9.2",
3
+ "version": "4.10.0",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -42,6 +42,7 @@
42
42
  "chai": "4.2.0",
43
43
  "cheerio": "1.0.0-rc.10",
44
44
  "dotenv": "8.2.0",
45
+ "deep-equal-in-any-order": "^2.0.0",
45
46
  "mocha": "9.2.0",
46
47
  "puppeteer": "13.1.3",
47
48
  "qs": "6.10.2",
@@ -132,3 +132,35 @@ Scenario: tap element with an error
132
132
  """
133
133
  Given I start the interview at "test_taps"
134
134
  And I tap the "Tests-not_there-tab" tab
135
+
136
+ @i5 @decoding-test @object-multiselect
137
+ Scenario: Base64 encoded corner cases are decoded correctly
138
+ Given I start the interview at "test_decoding"
139
+ And I get to "end screen" with this data:
140
+ | var | value | trigger |
141
+ | object_multiselect_test['obj_opt_1'] | True | |
142
+ | object_multiselect_test['obj_opt_3'] | True | |
143
+ | multiselect_test['hi'] | True | |
144
+ | multiselect_test['dear'] | True | |
145
+ | object_radio_field | object_radio_choice_one | |
146
+ | object_checkboxes_test['obj_chkbx_opt_1'] | True | |
147
+ | object_dropdown | obj_opt_2 | |
148
+ | checkbox_dict['single_quote_key']['🁳🧴🯍🥫🅍🗎🹇🗷🌲🯵'] | True | |
149
+
150
+ # This test should fail because it's target id is never found, but that also means
151
+ # it has succeeded because it didn't try to keep looking for the id by pressing 'restart'
152
+ # to continue.
153
+ @fast @i6
154
+ Scenario: Fails as it doesn't try to 'continue' with a restart button
155
+ Given the final Scenario status should be "failed"
156
+ And the Scenario report should include:
157
+ """
158
+ The test got stuck on "kickout-screen"
159
+ """
160
+ Given the max seconds for each step in this scenario is 10
161
+ Given I start the interview at "test_kickout"
162
+ And I get to "doesnt exist" with this data:
163
+ | var | value | trigger |
164
+ | user_choice | wrong | |
165
+
166
+
@@ -6,13 +6,13 @@ Feature: Reports show the right things
6
6
  # rp for report pass
7
7
 
8
8
  # ===============================
9
- # Reoprts for "failed" Scenarios
9
+ # Reports for "failed" Scenarios
10
10
  # ===============================
11
11
 
12
12
  # ---- Exceptions in steps.js ----
13
13
 
14
14
  @fast @rf1 @error
15
- Scenario: Fail with missng language link
15
+ Scenario: Fail with missing language link
16
16
  Given the final Scenario status should be "failed"
17
17
  Given the Scenario report should include:
18
18
  """
@@ -106,7 +106,7 @@ Scenario: Fail with missing error message
106
106
 
107
107
  # TODO: Check this with validation code failure too
108
108
  @fast @rf10 @error
109
- Scenario: Fail with was uexepctedly not able to continue for invalid field input message
109
+ Scenario: Fail with was unexpectedly not able to continue for invalid field input message
110
110
  Given the final Scenario status should be "failed"
111
111
  Given the Scenario report should include:
112
112
  """
@@ -544,3 +544,14 @@ Scenario: Sign in to server successfully
544
544
  Signed in
545
545
  """
546
546
    Given I sign in with the email "USER1_EMAIL" and the password "USER1_PASSWORD"
547
+
548
+ @rp4 @table
549
+ Scenario: Report doesn't complain about `there_is_another | False` in a table.
550
+ Given the Scenario report should not include:
551
+ """
552
+ The attribute `.there_is_another` is invalid in story table tests
553
+ """
554
+ Given I start the interview at "test_gather"
555
+ And I get to "end screen" with this data:
556
+ | var | value | trigger |
557
+ | users.there_is_another | False | users.there_is_another |
@@ -15,7 +15,7 @@ fields.standard = [
15
15
  }
16
16
  ],
17
17
  "type": "checkbox",
18
- "trigger": "direct_standard_fields"
18
+ "trigger": "direct_standard_fields",
19
19
  },
20
20
  {
21
21
  "selector": "#daquestion input[name=\"Y2hlY2tib3hlc19vdGhlcltCJ1kyaGxZMnRpYjNoZmIzUm9aWEpmYjNCMFh6RSdd\"][value=\"True\"][id=\"Y2hlY2tib3hlc19vdGhlcg_0\"][class=\"dafield1 danon-nota-checkbox da-to-labelauty checkbox-icon labelauty da-active-invisible dafullwidth\"]",
@@ -25,13 +25,9 @@ fields.standard = [
25
25
  "var": "checkboxes_other['checkbox_other_opt_1']",
26
26
  "value": "True"
27
27
  },
28
- {
29
- "var": "checkboxes_other['r\u0017���1��az����']",
30
- "value": "True"
31
- }
32
28
  ],
33
29
  "type": "checkbox",
34
- "trigger": "direct_standard_fields"
30
+ "trigger": "direct_standard_fields",
35
31
  },
36
32
  {
37
33
  "selector": "#daquestion input[name=\"Y2hlY2tib3hlc19vdGhlcltCJ1kyaGxZMnRpYjNoZmIzUm9aWEpmYjNCMFh6SSdd\"][value=\"True\"][id=\"Y2hlY2tib3hlc19vdGhlcg_1\"][class=\"dafield1 danon-nota-checkbox da-to-labelauty checkbox-icon labelauty da-active-invisible dafullwidth\"]",
@@ -41,13 +37,9 @@ fields.standard = [
41
37
  "var": "checkboxes_other['checkbox_other_opt_2']",
42
38
  "value": "True"
43
39
  },
44
- {
45
- "var": "checkboxes_other['r\u0017���1��az����']",
46
- "value": "True"
47
- }
48
40
  ],
49
41
  "type": "checkbox",
50
- "trigger": "direct_standard_fields"
42
+ "trigger": "direct_standard_fields",
51
43
  },
52
44
  {
53
45
  "selector": "#daquestion input[name=\"Y2hlY2tib3hlc19vdGhlcltCJ1kyaGxZMnRpYjNoZmIzUm9aWEpmYjNCMFh6TSdd\"][value=\"True\"][id=\"Y2hlY2tib3hlc19vdGhlcg_2\"][class=\"dafield1 danon-nota-checkbox da-to-labelauty checkbox-icon labelauty da-active-invisible dafullwidth\"]",
@@ -57,13 +49,9 @@ fields.standard = [
57
49
  "var": "checkboxes_other['checkbox_other_opt_3']",
58
50
  "value": "True"
59
51
  },
60
- {
61
- "var": "checkboxes_other['r\u0017���1��az����']",
62
- "value": "True"
63
- }
64
52
  ],
65
53
  "type": "checkbox",
66
- "trigger": "direct_standard_fields"
54
+ "trigger": "direct_standard_fields",
67
55
  },
68
56
  {
69
57
  "selector": "#daquestion input[name=\"_ignore1\"][id=\"labelauty-294201\"][class=\"dafield1 danota-checkbox da-to-labelauty checkbox-icon labelauty da-active-invisible dafullwidth\"]",
@@ -75,7 +63,7 @@ fields.standard = [
75
63
  }
76
64
  ],
77
65
  "type": "checkbox",
78
- "trigger": "direct_standard_fields"
66
+ "trigger": "direct_standard_fields",
79
67
  },
80
68
  {
81
69
  "selector": "#daquestion input[name=\"cmFkaW9feWVzbm8\"][value=\"True\"][id=\"cmFkaW9feWVzbm8_0\"][class=\"da-to-labelauty labelauty da-active-invisible dafullwidth\"]",
@@ -87,7 +75,7 @@ fields.standard = [
87
75
  }
88
76
  ],
89
77
  "type": "radio",
90
- "trigger": "direct_standard_fields"
78
+ "trigger": "direct_standard_fields",
91
79
  },
92
80
  {
93
81
  "selector": "#daquestion input[name=\"cmFkaW9feWVzbm8\"][value=\"False\"][id=\"cmFkaW9feWVzbm8_1\"][class=\"da-to-labelauty labelauty da-active-invisible dafullwidth\"]",
@@ -99,7 +87,7 @@ fields.standard = [
99
87
  }
100
88
  ],
101
89
  "type": "radio",
102
- "trigger": "direct_standard_fields"
90
+ "trigger": "direct_standard_fields",
103
91
  },
104
92
  {
105
93
  "selector": "#daquestion input[name=\"cmFkaW9fb3RoZXI\"][value=\"radio_other_opt_1\"][id=\"cmFkaW9fb3RoZXI_0\"][class=\"da-to-labelauty labelauty da-active-invisible dafullwidth\"]",
@@ -111,7 +99,7 @@ fields.standard = [
111
99
  }
112
100
  ],
113
101
  "type": "radio",
114
- "trigger": "direct_standard_fields"
102
+ "trigger": "direct_standard_fields",
115
103
  },
116
104
  {
117
105
  "selector": "#daquestion input[name=\"cmFkaW9fb3RoZXI\"][value=\"radio_other_opt_2\"][id=\"cmFkaW9fb3RoZXI_1\"][class=\"da-to-labelauty labelauty da-active-invisible dafullwidth\"]",
@@ -123,7 +111,7 @@ fields.standard = [
123
111
  }
124
112
  ],
125
113
  "type": "radio",
126
- "trigger": "direct_standard_fields"
114
+ "trigger": "direct_standard_fields",
127
115
  },
128
116
  {
129
117
  "selector": "#daquestion input[name=\"cmFkaW9fb3RoZXI\"][value=\"radio_other_opt_3\"][id=\"cmFkaW9fb3RoZXI_2\"][class=\"da-to-labelauty labelauty da-active-invisible dafullwidth\"]",
@@ -135,7 +123,7 @@ fields.standard = [
135
123
  }
136
124
  ],
137
125
  "type": "radio",
138
- "trigger": "direct_standard_fields"
126
+ "trigger": "direct_standard_fields",
139
127
  },
140
128
  {
141
129
  "selector": "#daquestion input[name=\"dGV4dF9pbnB1dA\"][id=\"dGV4dF9pbnB1dA\"][class=\"form-control\"]",
@@ -147,7 +135,7 @@ fields.standard = [
147
135
  }
148
136
  ],
149
137
  "type": "text",
150
- "trigger": "direct_standard_fields"
138
+ "trigger": "direct_standard_fields",
151
139
  },
152
140
  {
153
141
  "selector": "#daquestion textarea[name=\"dGV4dGFyZWE\"][id=\"dGV4dGFyZWE\"][class=\"form-control\"]",
@@ -159,7 +147,7 @@ fields.standard = [
159
147
  }
160
148
  ],
161
149
  "type": "",
162
- "trigger": "direct_standard_fields"
150
+ "trigger": "direct_standard_fields",
163
151
  },
164
152
  {
165
153
  "selector": "#daquestion input[name=\"ZGF0ZV9pbnB1dA\"][id=\"ZGF0ZV9pbnB1dA\"][class=\"form-control\"]",
@@ -171,7 +159,7 @@ fields.standard = [
171
159
  }
172
160
  ],
173
161
  "type": "date",
174
- "trigger": "direct_standard_fields"
162
+ "trigger": "direct_standard_fields",
175
163
  },
176
164
  {
177
165
  "selector": "#daquestion select[name=\"ZHJvcGRvd25fdGVzdA\"][id=\"ZHJvcGRvd25fdGVzdA\"][class=\"form-select dasingleselect\"]",
@@ -181,37 +169,21 @@ fields.standard = [
181
169
  "var": "dropdown_test",
182
170
  "value": ""
183
171
  },
184
- {
185
- "var": "dropdown_test",
186
- "value": ""
187
- },
188
172
  {
189
173
  "var": "dropdown_test",
190
174
  "value": "dropdown_opt_1"
191
175
  },
192
- {
193
- "var": "dropdown_test",
194
- "value": "v�)v�'��m�"
195
- },
196
176
  {
197
177
  "var": "dropdown_test",
198
178
  "value": "dropdown_opt_2"
199
179
  },
200
- {
201
- "var": "dropdown_test",
202
- "value": "v�)v�'��m�"
203
- },
204
180
  {
205
181
  "var": "dropdown_test",
206
182
  "value": "dropdown_opt_3"
207
183
  },
208
- {
209
- "var": "dropdown_test",
210
- "value": "v�)v�'��m�"
211
- }
212
184
  ],
213
185
  "type": "",
214
- "trigger": "direct_standard_fields"
186
+ "trigger": "direct_standard_fields",
215
187
  },
216
188
  {
217
189
  "selector": "#daquestion button[name=\"ZGlyZWN0X3N0YW5kYXJkX2ZpZWxkcw\"][value=\"True\"][class=\"btn btn-da btn-primary\"]",
@@ -223,7 +195,7 @@ fields.standard = [
223
195
  }
224
196
  ],
225
197
  "type": "submit",
226
- "trigger": "direct_standard_fields"
198
+ "trigger": "direct_standard_fields",
227
199
  }
228
200
  ]; // ends fields.standard
229
201
 
@@ -276,10 +248,6 @@ fields.show_if = [
276
248
  "var": "showif_checkboxes_other['showif_checkboxes_nota_1']",
277
249
  "value": "True"
278
250
  },
279
- {
280
- "var": "showif_checkboxes_other['�\u001a0��܅�$n�^��赯�']",
281
- "value": "True"
282
- }
283
251
  ],
284
252
  "type": "checkbox",
285
253
  "trigger": "direct_showifs"
@@ -292,10 +260,6 @@ fields.show_if = [
292
260
  "var": "showif_checkboxes_other['showif_checkboxes_nota_2']",
293
261
  "value": "True"
294
262
  },
295
- {
296
- "var": "showif_checkboxes_other['�\u001a0��܅�$n�^��赯�']",
297
- "value": "True"
298
- }
299
263
  ],
300
264
  "type": "checkbox",
301
265
  "trigger": "direct_showifs"
@@ -308,10 +272,6 @@ fields.show_if = [
308
272
  "var": "showif_checkboxes_other['showif_checkboxes_nota_3']",
309
273
  "value": "True"
310
274
  },
311
- {
312
- "var": "showif_checkboxes_other['�\u001a0��܅�$n�^��赯�']",
313
- "value": "True"
314
- }
315
275
  ],
316
276
  "type": "checkbox",
317
277
  "trigger": "direct_showifs"
@@ -420,42 +380,22 @@ fields.show_if = [
420
380
  "var": "showif_dropdown",
421
381
  "value": ""
422
382
  },
423
- {
424
- "var": "showif_dropdown",
425
- "value": ""
426
- },
427
383
  {
428
384
  "var": "showif_dropdown",
429
385
  "value": "showif_dropdown_1"
430
386
  },
431
- {
432
- "var": "showif_dropdown",
433
- "value": "�\u001a0��ݮ�]�\t�"
434
- },
435
387
  {
436
388
  "var": "showif_dropdown",
437
389
  "value": "showif_dropdown_2"
438
390
  },
439
- {
440
- "var": "showif_dropdown",
441
- "value": "�\u001a0��ݮ�]�\t�"
442
- },
443
391
  {
444
392
  "var": "showif_dropdown",
445
393
  "value": "showif_dropdown_3"
446
394
  },
447
- {
448
- "var": "showif_dropdown",
449
- "value": "�\u001a0��ݮ�]�\t�"
450
- },
451
395
  {
452
396
  "var": "showif_dropdown",
453
397
  "value": "showif_dropdown_4"
454
398
  },
455
- {
456
- "var": "showif_dropdown",
457
- "value": "�\u001a0��ݮ�]�\t�"
458
- }
459
399
  ],
460
400
  "type": "",
461
401
  "trigger": "direct_showifs"
@@ -725,10 +665,6 @@ fields.object_dropdown = [
725
665
  "var": "object_dropdown",
726
666
  "value": ""
727
667
  },
728
- {
729
- "var": "object_dropdown",
730
- "value": ""
731
- },
732
668
  {
733
669
  "var": "object_dropdown",
734
670
  "value": "b2JqX29wdF8x"
@@ -766,6 +702,70 @@ fields.object_dropdown = [
766
702
  }
767
703
  ];
768
704
 
705
+ // ============================
706
+ // multiselect created with objects
707
+ // ============================
708
+ // ```
709
+ // - Something: some_var
710
+ // datatype: object
711
+ // choices:
712
+ // - obj1
713
+ // - obj2
714
+ // ```
715
+ fields.object_multiselect = [
716
+ {
717
+ "selector": "#daquestion select[name=\"b2JqZWN0X211bHRpc2VsZWN0\"][id=\"b2JqZWN0X211bHRpc2VsZWN0\"][class=\"form-control damultiselect daobject\"]",
718
+ "tag": "select",
719
+ "guesses": [
720
+ {
721
+ "var": "object_multiselect['b2JqX29wdF8x']",
722
+ "value": "True"
723
+ },
724
+ {
725
+ "var": "object_multiselect['obj_opt_1']",
726
+ "value": "True"
727
+ },
728
+ {
729
+ "var": "object_multiselect['b2JqX29wdF8y']",
730
+ "value": "True"
731
+ },
732
+ {
733
+ "var": "object_multiselect['obj_opt_2']",
734
+ "value": "True"
735
+ },
736
+ {
737
+ "var": "object_multiselect['b2JqX29wdF8z']",
738
+ "value": "True"
739
+ },
740
+ {
741
+ "var": "object_multiselect['obj_opt_3']",
742
+ "value": "True"
743
+ },
744
+ ],
745
+ "type": "",
746
+ "trigger": "object_multiselect"
747
+ },
748
+ {
749
+ "guesses": [
750
+ {
751
+ "value": "True",
752
+ "var": "object_multiselect.gathered"
753
+ }
754
+ ],
755
+ "selector": "#daquestion input[name=\"b2JqZWN0X211bHRpc2VsZWN0LmdhdGhlcmVk\"][value=\"True\"]",
756
+ "tag": "input",
757
+ "trigger": "object_multiselect",
758
+ "type": "hidden"
759
+ },
760
+ {
761
+ "selector": "#daquestion button[class=\"btn btn-da btn-primary\"]",
762
+ "tag": "button",
763
+ "guesses": [],
764
+ "type": "submit",
765
+ "trigger": "object_multiselect"
766
+ },
767
+ ];
768
+
769
769
 
770
770
  // ============================
771
771
  // mixed quotes
@@ -779,10 +779,6 @@ fields.mixed_quotes = [
779
779
  "var": "single_quote_dict['single_quote_key']['sq_one']",
780
780
  "value": "True"
781
781
  },
782
- {
783
- "var": "single_quote_dict['single_quote_key']['���']",
784
- "value": "True"
785
- }
786
782
  ],
787
783
  "type": "checkbox",
788
784
  "trigger": "double_quote_dict['double_quote_key']"
@@ -795,10 +791,6 @@ fields.mixed_quotes = [
795
791
  "var": "single_quote_dict['single_quote_key']['sq_two']",
796
792
  "value": "True"
797
793
  },
798
- {
799
- "var": "single_quote_dict['single_quote_key']['����']",
800
- "value": "True"
801
- }
802
794
  ],
803
795
  "type": "checkbox",
804
796
  "trigger": "double_quote_dict['double_quote_key']"
@@ -811,10 +803,6 @@ fields.mixed_quotes = [
811
803
  "var": "single_quote_dict['single_quote_key']['sq_three']",
812
804
  "value": "True"
813
805
  },
814
- {
815
- "var": "single_quote_dict['single_quote_key']['��톷�']",
816
- "value": "True"
817
- }
818
806
  ],
819
807
  "type": "checkbox",
820
808
  "trigger": "double_quote_dict['double_quote_key']"
@@ -839,10 +827,6 @@ fields.mixed_quotes = [
839
827
  "var": "double_quote_dict[\"double_quote_key\"]['dq_one']",
840
828
  "value": "True"
841
829
  },
842
- {
843
- "var": "double_quote_dict[\"double_quote_key\"]['v��']",
844
- "value": "True"
845
- }
846
830
  ],
847
831
  "type": "checkbox",
848
832
  "trigger": "double_quote_dict['double_quote_key']"
@@ -855,10 +839,6 @@ fields.mixed_quotes = [
855
839
  "var": "double_quote_dict[\"double_quote_key\"]['dq_two']",
856
840
  "value": "True"
857
841
  },
858
- {
859
- "var": "double_quote_dict[\"double_quote_key\"]['v���']",
860
- "value": "True"
861
- }
862
842
  ],
863
843
  "type": "checkbox",
864
844
  "trigger": "double_quote_dict['double_quote_key']"
@@ -871,10 +851,6 @@ fields.mixed_quotes = [
871
851
  "var": "double_quote_dict[\"double_quote_key\"]['dq_three']",
872
852
  "value": "True"
873
853
  },
874
- {
875
- "var": "double_quote_dict[\"double_quote_key\"]['v�톷�']",
876
- "value": "True"
877
- }
878
854
  ],
879
855
  "type": "checkbox",
880
856
  "trigger": "double_quote_dict['double_quote_key']"
@@ -1,11 +1,15 @@
1
1
  const chai = require('chai');
2
2
  const expect = chai.expect;
3
+ const deepEqualInAnyOrder = require('deep-equal-in-any-order');
4
+ chai.use(deepEqualInAnyOrder);
3
5
  // We need jest or something, right? To do fancy stuff.
4
6
 
5
7
  const html = require('./html.fixtures.js');
6
8
  const fields = require('./fields.fixtures.js');
7
9
  const scope = require('../../lib/scope.js');
8
10
  const getAllFields = scope.getAllFields;
11
+
12
+ chai.config.truncateThreshold = 0
9
13
  // TODO: Rearrange fixtures so related fixtures are next to each other? e.g.
10
14
  // buttons_yesno.html, buttons_yesno.fields, buttons_yesno.table, buttons_yesno.matches
11
15
  // May make more files both to make and to import, but may be more useful
@@ -29,7 +33,9 @@ const getAllFields = scope.getAllFields;
29
33
  it(`creates the right data for standard fields`, async function() {
30
34
  // 18 fields (03/15/21)
31
35
  let result = await getAllFields( scope, { html: html.standard });
32
- expect( result ).to.deep.equal( fields.standard );
36
+ //console.log(`${ JSON.stringify(result) }`);
37
+ //console.log(`-----------------\n${ JSON.stringify(fields.standard) }`);
38
+ expect( result ).to.deep.equalInAnyOrder( fields.standard );
33
39
  });
34
40
 
35
41
 
@@ -38,7 +44,7 @@ it(`creates the right data for standard fields`, async function() {
38
44
  // ============================
39
45
  it(`creates the right data for 'show if' fields`, async function() {
40
46
  let result = await getAllFields( scope, { html: html.show_if });
41
- expect( result ).to.deep.equal( fields.show_if );
47
+ expect( result ).to.deep.equalInAnyOrder( fields.show_if );
42
48
  });
43
49
 
44
50
 
@@ -48,25 +54,25 @@ it(`creates the right data for 'show if' fields`, async function() {
48
54
  it(`creates the right data for one continue button`, async function() {
49
55
  // `continue button field:`
50
56
  let result = await getAllFields( scope, { html: html.button_continue });
51
- expect( result ).to.deep.equal( fields.button_continue );
57
+ expect( result ).to.deep.equalInAnyOrder( fields.button_continue );
52
58
  });
53
59
 
54
60
  it(`creates the right data for yesnomaybe buttons`, async function() {
55
61
  // `yesnomaybe:`
56
62
  let result = await getAllFields( scope, { html: html.buttons_yesnomaybe });
57
- expect( result ).to.deep.equal( fields.buttons_yesnomaybe );
63
+ expect( result ).to.deep.equalInAnyOrder( fields.buttons_yesnomaybe );
58
64
  });
59
65
 
60
66
  it(`creates the right data for other mutiple choice continue buttons`, async function() {
61
67
  // `field:` and `buttons:`
62
68
  let result = await getAllFields( scope, { html: html.buttons_other });
63
- expect( result ).to.deep.equal( fields.buttons_other );
69
+ expect( result ).to.deep.equalInAnyOrder( fields.buttons_other );
64
70
  });
65
71
 
66
72
  it(`creates the right data for an event action button`, async function() {
67
73
  // `field:` and `action buttons:`
68
74
  let result = await getAllFields( scope, { html: html.buttons_event_action });
69
- expect( result ).to.deep.equal( fields.buttons_event_action );
75
+ expect( result ).to.deep.equalInAnyOrder( fields.buttons_event_action );
70
76
  });
71
77
 
72
78
 
@@ -76,7 +82,7 @@ it(`creates the right data for an event action button`, async function() {
76
82
  // x[i].name.first
77
83
  it(`creates the right data for a multi-proxy name (x[i])`, async function() {
78
84
  let result = await getAllFields( scope, { html: html.proxies_xi });
79
- expect( result ).to.deep.equal( fields.proxies_xi );
85
+ expect( result ).to.deep.equalInAnyOrder( fields.proxies_xi );
80
86
  });
81
87
 
82
88
  // Multiple proxies by the same name are on the list (because of a loop)
@@ -84,7 +90,7 @@ it(`creates the right data for a multi-proxy name (x[i])`, async function() {
84
90
  it(`creates the right data for a multi-proxy name (x[i]) again for consistency`, async function() {
85
91
  // same data as `proxies_xi`, but have this here for consistency
86
92
  let result = await getAllFields( scope, { html: html.proxies_multi });
87
- expect( result ).to.deep.equal( fields.proxies_multi );
93
+ expect( result ).to.deep.equalInAnyOrder( fields.proxies_multi );
88
94
  });
89
95
 
90
96
 
@@ -94,7 +100,7 @@ it(`creates the right data for a multi-proxy name (x[i]) again for consistency`,
94
100
  it(`creates the right data for a signature field`, async function() {
95
101
  // `field:` and `action buttons:`
96
102
  let result = await getAllFields( scope, { html: html.signature });
97
- expect( result ).to.deep.equal( fields.signature );
103
+ expect( result ).to.deep.equalInAnyOrder( fields.signature );
98
104
  });
99
105
 
100
106
 
@@ -109,7 +115,7 @@ it(`creates the right data for a signature field`, async function() {
109
115
  // ```
110
116
  it(`creates the right data for a 'choices:' field`, async function() {
111
117
  let result = await getAllFields( scope, { html: html.choices });
112
- expect( result ).to.deep.equal( fields.choices );
118
+ expect( result ).to.deep.equalInAnyOrder( fields.choices );
113
119
  });
114
120
 
115
121
 
@@ -125,10 +131,9 @@ it(`creates the right data for a 'choices:' field`, async function() {
125
131
  // ```
126
132
  it(`creates the right data for a dropdown created with an object`, async function() {
127
133
  let result = await getAllFields( scope, { html: html.object_dropdown });
128
- expect( result ).to.deep.equal( fields.object_dropdown );
134
+ expect( result ).to.deep.equalInAnyOrder(fields.object_dropdown );
129
135
  });
130
136
 
131
-
132
137
  // ============================
133
138
  // DOM for mixed quotes test in getMatchingRows.test.js
134
139
  // ============================
@@ -138,5 +143,5 @@ it(`creates the right data for a dropdown created with an object`, async functio
138
143
  // following test is mostly useful for that second purpose.
139
144
  it(`creates the right data for a question whose table will have mixed quotes`, async function() {
140
145
  let result = await getAllFields( scope, { html: html.mixed_quotes });
141
- expect( result ).to.deep.equal( fields.mixed_quotes );
146
+ expect( result ).to.deep.equalInAnyOrder( fields.mixed_quotes );
142
147
  });
@@ -9,9 +9,9 @@
9
9
 
10
10
  let test_filename = function ( expected_prefix, result ) {
11
11
  /* Test a name to see if it's valid output with a language defined. */
12
- let expected_name_regex_str = `^${ expected_prefix }-${ names.base_filename }-\.{40}$`;
12
+ let expected_name_regex_str = `^${ expected_prefix }-${ names.base_filename }-\.{32}$`;
13
13
  if ( expected_prefix == `` ) {
14
- expected_name_regex_str = `^${ names.base_filename }-\.{40}$`;
14
+ expected_name_regex_str = `^${ names.base_filename }-\.{32}$`;
15
15
  }
16
16
  let regex = new RegExp( expected_name_regex_str );
17
17
 
@@ -727,7 +727,6 @@ html.object_dropdown = `
727
727
  </form>
728
728
  </div>`;
729
729
 
730
-
731
730
  html.mixed_quotes = `
732
731
  <div id="daquestion" aria-labelledby="dapagetitle" role="main" class="tab-pane fade show active offset-xl-3 offset-lg-3 col-xl-6 col-lg-6 offset-md-2 col-md-8">
733
732
  <div data-variable="ZG91YmxlX3F1b3RlX2RpY3RbJ2RvdWJsZV9xdW90ZV9rZXknXQ" id="trigger" aria-hidden="true" style="display: none;"></div>