@suffolklitlab/alkiln 5.8.1-feat2columns1 → 5.8.1-feat2columns3

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.
@@ -88,4 +88,5 @@ jobs:
88
88
  #### Developer note: Useful for temporarily shortening ALKiln tests
89
89
  #### to debug the docker install step
90
90
  # ALKILN_TAG_EXPRESSION: @a_tag_expression
91
+ ALKILN_VERSION: 5.8.1-feat2columns3
91
92
 
@@ -55,4 +55,4 @@ jobs:
55
55
  #### to debug the docker install step
56
56
  # ALKILN_TAG_EXPRESSION: @a_tag_expression
57
57
  #### Developer note: you probalby don't need this
58
- # ALKILN_VERSION: 5.7.0-feature-tap1
58
+ ALKILN_VERSION: 5.8.1-feat2columns3
package/CHANGELOG.md CHANGED
@@ -45,6 +45,10 @@ Format:
45
45
 
46
46
  <!-- ## [Unreleased] -->
47
47
 
48
+ ### Added
49
+
50
+ - 2-column Story Tables that work without the trigger variable. Now column 1, "var", should only contain the actual variable name with no proxies (x, i, j, etc). For example, instead of `x[i].name.first` the var column should now contain `users[0].name.first`. Column 2 is the same as it was before - the answer for the field. See [#582](https://github.com/SuffolkLITLab/ALKiln/issues/582).
51
+
48
52
  ## [5.8.1] - 2023-12-28
49
53
 
50
54
  ### Fixed
package/lib/scope.js CHANGED
@@ -292,6 +292,9 @@ module.exports = {
292
292
  if ( !elem ) { reports.addToReport(scope, { type: `error`, value: msg }); }
293
293
  expect( elem, msg ).to.exist;
294
294
 
295
+ // Get the text on the button or link for a potential error message later
296
+ let tapperText = await (await elem.getProperty('textContent')).jsonValue();
297
+
295
298
  click_promise = click_promise || elem.click();
296
299
 
297
300
  // TODO: implement and use scope.handle_possible_timeout()
@@ -308,14 +311,11 @@ module.exports = {
308
311
  } catch ( error ) {
309
312
 
310
313
  if ( error.name === `TimeoutError` ) {
311
- let non_reload_report_msg = `ALKiln tapped "${ tapperText }", but the interview took to long to finish.`;
314
+ let non_reload_report_msg = `After ALKiln tapped "${ tapperText }" it took too long to load the next page.`;
312
315
  await scope.handle_page_timeout_error( scope, { non_reload_report_msg, error });
313
316
  } else {
314
-
315
- // Get the text on the button or link for a potential error message later
316
- let tapperText = await (await elem.getProperty('textContent')).jsonValue();
317
317
  // Throw any non-timeout error
318
- let err_msg = `Error occurred when ALKiln tried to tap "${ tapperText }".`
318
+ let err_msg = `An error occurred when ALKiln tried to tap "${ tapperText }".`
319
319
  reports.addToReport( scope, { type: `error`, value: err_msg });
320
320
  throw error;
321
321
  } // ends if error is timeout error
@@ -464,9 +464,9 @@ module.exports = {
464
464
 
465
465
  throw_server_was_reloading_error: async function( scope ) {//, { server_timeout_id, timeout_message } ) {
466
466
  /* Throw the server reload error with appropriate side effects. */
467
- let reload_failure_msg = `The test was unable to continue because the `
468
- + `server was reloading. ALKiln will try this Scenario a total of 2 times. An `
469
- + `error will still be printed for this attempt.`;
467
+ let reload_failure_msg = `The test was unable to continue because it `
468
+ + `took too long to reach the server. ALKiln will try this Scenario a total of `
469
+ + `2 times. A full error will be printed for the second attempt.`;
470
470
  reports.addToReport( scope, { type: `error`, value: reload_failure_msg });
471
471
  // reports.addToReport( scope, { type: `error`, value: timeout_message });
472
472
  // clearTimeout( server_timeout_id );
@@ -721,51 +721,6 @@ module.exports = {
721
721
  return result;
722
722
  }, // Ends scope.getLoadData()
723
723
 
724
- // three_column_table_warning: async function ( scope, { var_data=null, raw_var_data=null }) {
725
-
726
- // },
727
-
728
- // normalize_table_base: async function ( scope, { var_data=null, raw_var_data=null }) {
729
- // /* Return data derived from cucumber variable-setting data, from either an object format
730
- // * (var_data) or an array format (raw_var_data). `var_data` only is used when calling
731
- // * normalizeTable internally.
732
- // *
733
- // * Takes care of the shared funcitonality that needs to happen to all tables.
734
- // */
735
-
736
- // if (!var_data && ! raw_var_data) {
737
- // let no_data_msg = `ALKiln Internal error: normalizeTable called w/o var_data or raw_var_data:` +
738
- // ` open an issue at https://github.com/SuffolkLITLab/ALKiln/issues/new if you see this!`;
739
- // reports.addToReport( scope, { type: `error`, value: no_data_msg });
740
- // throw Error(no_data_msg);
741
- // }
742
-
743
- // // Always use the raw var data if present; it has the full first row
744
- // if (raw_var_data) {
745
- // var_data = raw_var_data.hashes();
746
- // if (!('var' in var_data[0]) || !('value' in var_data[0])) {
747
- // let raw_array = raw_var_data.raw();
748
- // var_data = [];
749
- // for (let row_raw of raw_array) {
750
- // if (row_raw.length < 2) {
751
- // let bad_table_msg = `Your Story Table has less than 2 columns.`
752
- // + ` Take another look at Story Table documentation at`
753
- // + ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#story-tables.`
754
- // reports.addToReport( scope, { type: `error`, value: bad_table_msg})
755
- // throw Error(bad_table_msg);
756
- // } else if (row_raw.length == 2) {
757
- // var_data.push({var: row_raw[0], value: row_raw[1]});
758
- // } else {
759
- // var_data.push({var: row_raw[0], value: row_raw[1], trigger: row_raw[2]});
760
- // }
761
- // }
762
- // }
763
- // }
764
-
765
-
766
-
767
- // },
768
-
769
724
  normalizeTable: async function ( scope, { var_data=null, raw_var_data=null }) {
770
725
  /* Return data derived from cucumber variable-setting data, from either an object format
771
726
  * (var_data) or an array format (raw_var_data). `var_data` only is used when calling
@@ -915,13 +870,6 @@ module.exports = {
915
870
  field_data.selector = `#daquestion canvas`;
916
871
  field_data.tag = `canvas`;
917
872
  not_decoded_name = $( `input[name="_save_as"]` ).attr( `value` );
918
-
919
- // field_data.guesses = [{
920
- // var: trigger,
921
- // value: '/sign',
922
- // }];
923
- // fields.push( field_data );
924
- // continue;
925
873
  }
926
874
 
927
875
  // The selector we'll use to set the DOM node's value later
@@ -1489,9 +1437,12 @@ module.exports = {
1489
1437
 
1490
1438
  let multiple_matches_msg = `Do you have a duplicate story table row? `
1491
1439
  + `Multiple story table rows match this field's variable name. `
1492
- + `The test will use the last row that matched. See `
1493
- + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
1494
- + `\nThe matching rows:\n${ rows_as_story_table.join(`\n`) }`
1440
+ + `The test will use the last row that matched.`
1441
+
1442
+ if ( trigger_might_be_needed ) {
1443
+ multiple_matches_msg += ` See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
1444
+ }
1445
+ multiple_matches_msg += `\nThe matching rows:\n${ rows_as_story_table.join(`\n`) }`
1495
1446
  + `\nThis field's data:\n${ JSON.stringify( field )}`;
1496
1447
  reports.addToReport(scope, { type: `warning`, value: multiple_matches_msg });
1497
1448
  }
@@ -2155,24 +2106,21 @@ module.exports = {
2155
2106
 
2156
2107
  // If all table rows must be used and any table row was not, error
2157
2108
  if ( ensure_all_vars_used ) {
2158
- let num_unused_rows = 0;
2159
2109
  for ( let row of var_data ) {
2160
2110
  if ( row.times_used === 0 ) {
2161
- num_unused_rows += 1;
2111
+ let values_used_status = 'The page is missing a value';
2162
2112
  let source_row = row;
2163
2113
  // TODO: Make the row object flatter so we don't need to dig in this way
2164
2114
  if ( row.source ) { source_row = row.source; }
2165
2115
  let original_row = source_row.original;
2166
- let msg = `Did not find a field on this page for the variable "${ original_row.var }" that could be set to "${ original_row.value }"`;
2167
- reports.addToReport(scope, { type: `warning`, value: msg });
2116
+ let { id } = await scope.examinePageID( scope, 'none to match' );
2117
+ let msg = `In a linear Step all the values you give MUST be filled in, but ALKiln was unable `
2118
+ + `to find a field on the page "${id}" for the variable "${ original_row.var }" that `
2119
+ + `could be set to "${ original_row.value }". See this Scenario's HTML file for details.`;
2120
+ reports.addToReport(scope, { type: `error`, value: msg });
2121
+ expect( values_used_status, msg ).to.equal( '' );
2168
2122
  }
2169
2123
  }
2170
- if ( num_unused_rows > 0 ) {
2171
- let { id } = await scope.examinePageID( scope, 'none to match' );
2172
- let msg = `Error on page "${id}". See screenshot and see text above for more details.`;
2173
- reports.addToReport(scope, { type: `error`, value: msg });
2174
- expect( num_unused_rows, msg ).to.equal( 0 );
2175
- }
2176
2124
  }
2177
2125
 
2178
2126
  // If didn't already press a continue button, press one now.
package/lib/steps.js CHANGED
@@ -297,7 +297,9 @@ Given(/the max(?:imum)? sec(?:ond)?s for each step (?:in this scenario )?is (\d+
297
297
  // Story
298
298
  //#####################################
299
299
  //#####################################
300
-
300
+ // TODO: Consider more permissive sentences:
301
+ // with(?: this data| these answers)?:?
302
+ // with(?:.*):?
301
303
  When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)" with this data:?/, {timeout: -1}, async (target_id, raw_var_data) => {
302
304
  /* NO CUCUMBER TIMEOUT. Must handle timeout ourselves:
303
305
  * https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/timeouts.md#disable-timeouts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "5.8.1-feat2columns1",
3
+ "version": "5.8.1-feat2columns3",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -1,5 +1,10 @@
1
1
  let fields = {};
2
2
 
3
+ /** The fixtures in here are used in at least 2 types of tests:
4
+ * - getAllFields
5
+ * - getMatchignRows
6
+ * */
7
+
3
8
  // ============================
4
9
  // Standard fields - no proxies, no showifs.
5
10
  // ============================
@@ -736,7 +741,8 @@ fields.buttons_event_action = [
736
741
  // ============================
737
742
  // Proxy vars (x, i, j, ...)
738
743
  // ============================
739
- // x[i].name.first
744
+ // First loop (first time this question is asked)
745
+ // x[i].name.first where i = 0
740
746
  fields.proxies_xi = [
741
747
  {
742
748
  "selector": "#daquestion input[name=\"eFtpXS5uYW1lLmZpcnN0\"][id=\"eFtpXS5uYW1lLmZpcnN0\"][class=\"form-control\"]",
@@ -764,8 +770,8 @@ fields.proxies_xi = [
764
770
  ];
765
771
 
766
772
  // Multiple proxies by the same name are in the story table (because of a loop)
767
- // Second loop (second page where this question is asked)
768
- // x[i].name.first
773
+ // Second loop (second time this question is asked)
774
+ // x[i].name.first where i = 1
769
775
  fields.proxies_multi = [
770
776
  {
771
777
  "selector": "#daquestion input[name=\"eFtpXS5uYW1lLmZpcnN0\"][id=\"eFtpXS5uYW1lLmZpcnN0\"][class=\"form-control\"]",
@@ -792,6 +798,89 @@ fields.proxies_multi = [
792
798
  }
793
799
  ];
794
800
 
801
+ // Proxy substitution instead of target variable
802
+ // First loop (first time this question is asked)
803
+ // x[i].name.first where i = 0
804
+ fields.proxy_substitution_i_is_0 = [
805
+ {
806
+ "selector": "#daquestion input[name=\"eFtpXS5uYW1lLmZpcnN0\"][id=\"eFtpXS5uYW1lLmZpcnN0\"][class=\"form-control\"]",
807
+ "tag": "input",
808
+ "guesses": [
809
+ {
810
+ "var": "eFtpXS5uYW1lLmZpcnN0",
811
+ "value": ""
812
+ },
813
+ {
814
+ "var": "x[i].name.first",
815
+ "value": ""
816
+ },
817
+ {
818
+ "var": "proxy_list[0].name.first",
819
+ "value": ""
820
+ }
821
+ ],
822
+ "type": "text",
823
+ "trigger": ""
824
+ },
825
+ {
826
+ "selector": "#daquestion button[id=\"da-continue-button\"][class=\"btn btn-da btn-primary\"]",
827
+ "tag": "button",
828
+ "guesses": [
829
+ {
830
+ "var": "da-continue-button",
831
+ "value": ""
832
+ },
833
+ {
834
+ "var": "u���{b��n�m�",
835
+ "value": ""
836
+ }
837
+ ],
838
+ "type": "submit",
839
+ "trigger": ""
840
+ }
841
+ ]
842
+
843
+ // Proxy substitution instead of target variable
844
+ // Second loop (second time this question is asked)
845
+ // x[i].name.first where i = 1
846
+ fields.proxy_substitution_i_is_1 = [
847
+ {
848
+ "selector": "#daquestion input[name=\"eFtpXS5uYW1lLmZpcnN0\"][id=\"eFtpXS5uYW1lLmZpcnN0\"][class=\"form-control\"]",
849
+ "tag": "input",
850
+ "guesses": [
851
+ {
852
+ "var": "eFtpXS5uYW1lLmZpcnN0",
853
+ "value": ""
854
+ },
855
+ {
856
+ "var": "x[i].name.first",
857
+ "value": ""
858
+ },
859
+ {
860
+ "var": "proxy_list[1].name.first",
861
+ "value": ""
862
+ }
863
+ ],
864
+ "type": "text",
865
+ "trigger": ""
866
+ },
867
+ {
868
+ "selector": "#daquestion button[id=\"da-continue-button\"][class=\"btn btn-da btn-primary\"]",
869
+ "tag": "button",
870
+ "guesses": [
871
+ {
872
+ "var": "da-continue-button",
873
+ "value": ""
874
+ },
875
+ {
876
+ "var": "u���{b��n�m�",
877
+ "value": ""
878
+ }
879
+ ],
880
+ "type": "submit",
881
+ "trigger": ""
882
+ }
883
+ ]
795
884
 
796
885
  // ============================
797
886
  // Signature
@@ -799,20 +888,23 @@ fields.proxies_multi = [
799
888
  // Signature page continue buttons are contained in a different DOM structure than regular ones
800
889
  // Also, there's an additional hidden continue button on the page with similar selectors
801
890
  fields.signature = [
802
- {
803
- "trigger": "signature_1",
804
- "selector": "#daquestion canvas",
805
- "tag": "canvas",
806
- "guesses": [
807
- {
808
- "var": "signature_1",
809
- "value": "/sign",
810
- }
811
- ],
812
- "type": ""
813
- },
814
-
815
- ];
891
+ {
892
+ "selector": "#daquestion canvas",
893
+ "tag": "canvas",
894
+ "guesses": [
895
+ {
896
+ "var": "c2lnbmF0dXJlXzE",
897
+ "value": ""
898
+ },
899
+ {
900
+ "var": "signature_1",
901
+ "value": ""
902
+ }
903
+ ],
904
+ "type": "",
905
+ "trigger": "signature_1"
906
+ }
907
+ ];
816
908
 
817
909
 
818
910
  // ============================
@@ -79,32 +79,43 @@ it(`creates the right data for an event action button`, async function() {
79
79
  // ============================
80
80
  // Proxy vars (x, i, j, ...)
81
81
  // ============================
82
- // x[i].name.first
83
- it(`creates the right data for a multi-proxy name (x[i])`, async function() {
82
+ // x[i].name.first where i = 0
83
+ it(`creates the right data for x[i] target variable where "i" is 0`, async function() {
84
84
  let result = await getAllFields( scope, { html: html.proxies_xi });
85
85
  expect( result ).to.deep.equalInAnyOrder( fields.proxies_xi );
86
86
  });
87
87
 
88
- // Multiple proxies by the same name are on the list (because of a loop)
89
- // x[i].name.first
90
- it(`creates the right data for a multi-proxy name (x[i]) again for consistency`, async function() {
91
- // same data as `proxies_xi`, but have this here for consistency
88
+ // x[i].name.first where i = 1
89
+ it(`creates the right data for x[i] target variable where "i" is 1`, async function() {
92
90
  let result = await getAllFields( scope, { html: html.proxies_multi });
93
91
  expect( result ).to.deep.equalInAnyOrder( fields.proxies_multi );
94
92
  });
95
93
 
96
94
 
97
- // Passing, but shouldn't be. Must implement poxie substitution for this too
98
- // // ============================
99
- // // Signature
100
- // // ============================
101
- // it(`creates the right data for a signature field`, async function() {
102
- // // `field:` and `action buttons:`
103
- // let result = await getAllFields( scope, { html: html.signature });
104
- // console.log(`=================\n${ JSON.stringify(fields.signature, null, 2) }`);
105
- // console.log(`-----------------\n${ JSON.stringify(result, null, 2) }`);
106
- // expect( result ).to.deep.equalInAnyOrder( fields.signature );
107
- // });
95
+ // ============================
96
+ // Proxy vars (x, i, j, ...)
97
+ // ============================
98
+ // x[i].name.first where i = 0
99
+ it(`creates the right data for x[i] proxy substitution where "i" is 0`, async function() {
100
+ let result = await getAllFields( scope, { html: html.proxy_substitution_i_is_0 });
101
+ expect( result ).to.deep.equalInAnyOrder( fields.proxy_substitution_i_is_0 );
102
+ });
103
+
104
+ // x[i].name.first where i = 1
105
+ it(`creates the right data for x[i] target variable where "i" is 1`, async function() {
106
+ let result = await getAllFields( scope, { html: html.proxy_substitution_i_is_1 });
107
+ expect( result ).to.deep.equalInAnyOrder( fields.proxy_substitution_i_is_1 );
108
+ });
109
+
110
+
111
+ // ============================
112
+ // Signature
113
+ // ============================
114
+ it(`creates the right data for a signature field`, async function() {
115
+ // `field:` and `action buttons:`
116
+ let result = await getAllFields( scope, { html: html.signature });
117
+ expect( result ).to.deep.equalInAnyOrder( fields.signature );
118
+ });
108
119
 
109
120
 
110
121
  // ============================
@@ -38,6 +38,8 @@ describe("Matches: For standard fields", async function() {
38
38
  let name = getSafeName( field );
39
39
  let result = await getMatchingRows( scope, { field, var_data: tables.standard });
40
40
  it( `finds the right matches for ${ name }`, async function() {
41
+ // console.log(`=================\n${ JSON.stringify(curr_matches) }`);
42
+ // console.log(`-----------------\n${ JSON.stringify(result) }`);
41
43
  expect( result ).to.deep.equal( curr_matches );
42
44
  });
43
45
  }
@@ -194,10 +196,10 @@ describe(`For mulitple rows with the same proxies`, async function() {
194
196
  describe(`For a second proxy loop page,`, async function() {
195
197
  for ( let test_i = 0; test_i < fields.proxies_multi.length; test_i++ ) {
196
198
  let field = fields.proxies_multi[ test_i ];
197
- let curr_matches = matches.proxies_missing_loop[ test_i ];
199
+ let curr_matches = matches.proxies_missing_table_row[ test_i ];
198
200
  let name = getSafeName( field );
199
201
  it( `does not find any matches when table row for second loop page is missing`, async function() {
200
- let result = await getMatchingRows( scope, { field, var_data: tables.proxies_missing_loop });
202
+ let result = await getMatchingRows( scope, { field, var_data: tables.proxies_missing_table_row });
201
203
  expect( result ).to.deep.equal( curr_matches );
202
204
  });
203
205
  }
@@ -216,6 +218,46 @@ describe(`For a second proxy loop page,`, async function() {
216
218
  }
217
219
  });
218
220
 
221
+ // Proxy substitutes (no trigger) first loop (i = 0)
222
+ describe(`For proxy substitution where "i" = 0`, async function() {
223
+ for ( let test_i = 0; test_i < fields.proxy_substitution_i_is_0.length; test_i++ ) {
224
+ let field = fields.proxy_substitution_i_is_0[ test_i ];
225
+ let curr_matches = matches.proxy_substitution_i_is_0[ test_i ];
226
+ let name = getSafeName( field );
227
+ it( `finds the right matches for ${ name }`, async function() {
228
+ let result = await getMatchingRows( scope, { field, var_data: tables.proxy_substitution_i_is_0 });
229
+ expect( result ).to.deep.equal( curr_matches );
230
+ });
231
+ }
232
+ });
233
+
234
+ // Multiple proxies by the same name are on the list (because of a loop)
235
+ // Proxy substitutes (no trigger) second loop (i = 1)
236
+ describe(`For proxy substitutions with mulitple rows of "i" where "i" = 1`, async function() {
237
+ for ( let test_i = 0; test_i < fields.proxy_substitution_i_is_1.length; test_i++ ) {
238
+ let field = fields.proxy_substitution_i_is_1[ test_i ];
239
+ let curr_matches = matches.proxy_substitution_i_is_1[ test_i ];
240
+ let name = getSafeName( field );
241
+ it( `finds the right matches for ${ name }`, async function() {
242
+ let result = await getMatchingRows( scope, { field, var_data: tables.proxy_substitution_i_is_1 });
243
+ expect( result ).to.deep.equal( curr_matches );
244
+ });
245
+ }
246
+ });
247
+
248
+ // Proxy substitution (no trigger) missing table row for second loop (i = 1)
249
+ describe(`For a second loop page with a proxy substitution Story Table,`, async function() {
250
+ for ( let test_i = 0; test_i < fields.proxies_multi.length; test_i++ ) {
251
+ let field = fields.proxies_multi[ test_i ];
252
+ let curr_matches = matches.proxy_substitution_missing_table_row[ test_i ];
253
+ let name = getSafeName( field );
254
+ it( `finds no matches when the Story Table row for that loop is missing`, async function() {
255
+ let result = await getMatchingRows( scope, { field, var_data: tables.proxy_substitution_missing_table_row });
256
+ expect( result ).to.deep.equal( curr_matches );
257
+ });
258
+ }
259
+ });
260
+
219
261
 
220
262
  // ============================
221
263
  // Signature
@@ -505,7 +505,8 @@ html.buttons_event_action = `
505
505
  // proxies correctly, but we don't need to worry about guessing the proxy
506
506
  // name. See https://github.com/plocket/docassemble-cucumber/pull/221
507
507
 
508
- // x[i].name.first
508
+ // First loop (first time this question appears)
509
+ // x[i].name.first (i = 0)
509
510
  html.proxies_xi = `
510
511
  <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">
511
512
  <div data-variable="cHJveHlfbGlzdFswXS5uYW1lLmZpcnN0" id="trigger" aria-hidden="true" style="display: none;"></div>
@@ -534,9 +535,8 @@ html.proxies_xi = `
534
535
  </form>
535
536
  </div>`;
536
537
 
537
- // Second page of above
538
- // Multiple proxies by the same name are on the list (because of a loop)
539
- // x[i].name.first
538
+ // Second loop (second time this question appears)
539
+ // x[i].name.first (i = 1)
540
540
  html.proxies_multi = `
541
541
  <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">
542
542
  <div data-variable="cHJveHlfbGlzdFsxXS5uYW1lLmZpcnN0" id="trigger" aria-hidden="true" style="display: none;"></div>
@@ -565,6 +565,68 @@ html.proxies_multi = `
565
565
  </form>
566
566
  </div>`;
567
567
 
568
+ // Proxy substitution, no target variable
569
+ // First loop (first time this question appears)
570
+ // x[i].name.first (i = 0)
571
+ html.proxy_substitution_i_is_0 = `
572
+ <div id="daquestion" aria-labelledby="dapagetitle" role="main" class="tab-pane fade show active offset-lg-3 col-lg-6 offset-md-2 col-md-8">
573
+ <div id="alkiln_proxy_var_values" data-generic_object="cHJveHlfbGlzdA" data-index_var_i="MA" data-index_var_j="" data-index_var_k="" data-index_var_l="" data-index_var_m="" data-index_var_n="" aria-hidden="true" style="display: none;"></div>
574
+ <form aria-labelledby="daMainQuestion" action="/interview?i=docassemble.playground12ALKilnTestALKiln5822columns1706994311264:test_pdf.yml" id="daform" class="form-horizontal daformfields" method="POST" novalidate="novalidate">
575
+ <div class="da-page-header">
576
+ <h1 class="h3" id="daMainQuestion">Proxy var 1</h1>
577
+ <div class="daclear"></div>
578
+ </div>
579
+ <div class="da-container da-form-group row darequired da-field-container da-field-container-datatype-text">
580
+ <label for="eFtpXS5uYW1lLmZpcnN0" class="col-md-4 col-form-label da-form-label datext-right" >first proxy name</label>
581
+ <div class="col-md-8 dafieldpart">
582
+ <input alt="Input box" class="form-control" type="text" name="eFtpXS5uYW1lLmZpcnN0" id="eFtpXS5uYW1lLmZpcnN0" required=""/>
583
+ </div>
584
+ </div>
585
+ <fieldset class="da-button-set da-field-buttons">
586
+ <legend class="visually-hidden">Press one of the following buttons:</legend>
587
+ <button class="btn btn-da btn-primary" id="da-continue-button" type="submit">Continue</button>
588
+ </fieldset>
589
+ <input type="hidden" name="csrf_toke" value="ImNlMGRmOWY5NmQ4MGRjMjI0ZDQyNGFjMWRiYjdiODAzOTk2ZThhY2Ui.Zb7dOA.Fvil0jDP4mebaBoPmtM7sXv6wSQ"/>
590
+ <input type="hidden" name="_event" value="WyJwcm94eV9saXN0WzBdLm5hbWUuZmlyc3QiXQ"/>
591
+ <input type="hidden" name="_question_name" value="ID proxy vars"/>
592
+ <input type="hidden" name="_tracker" value="3"/>
593
+ <input type="hidden" name="_datatypes" value="eyJlRnRwWFM1dVlXMWxMbVpwY25OMCI6ICJ0ZXh0In0"/>
594
+ <input type="hidden" name="_visible" value="" />
595
+ <input type="hidden" name="_varnames" value="eyJYMlpwWld4a1h6QSI6ICJlRnRwWFM1dVlXMWxMbVpwY25OMCJ9"/>
596
+ </form>
597
+ </div>`;
598
+
599
+ // Proxy substitution, no target variable
600
+ // Second loop (second time this question appears)
601
+ // x[i].name.first (i = 1)
602
+ html.proxy_substitution_i_is_1 = `
603
+ <div id="daquestion" aria-labelledby="dapagetitle" role="main" class="tab-pane fade show active offset-lg-3 col-lg-6 offset-md-2 col-md-8">
604
+ <div id="alkiln_proxy_var_values" data-generic_object="cHJveHlfbGlzdA" data-index_var_i="MQ" data-index_var_j="" data-index_var_k="" data-index_var_l="" data-index_var_m="" data-index_var_n="" aria-hidden="true" style="display: none;"></div>
605
+ <form aria-labelledby="daMainQuestion" action="/interview?i=docassemble.playground12ALKilnTestALKiln5822columns1706994311264:test_pdf.yml" id="daform" class="form-horizontal daformfields" method="POST" novalidate="novalidate">
606
+ <div class="da-page-header">
607
+ <h1 class="h3" id="daMainQuestion">Proxy var 2</h1>
608
+ <div class="daclear"></div>
609
+ </div>
610
+ <div class="da-container da-form-group row darequired da-field-container da-field-container-datatype-text">
611
+ <label for="eFtpXS5uYW1lLmZpcnN0" class="col-md-4 col-form-label da-form-label datext-right">second proxy name</label>
612
+ <div class="col-md-8 dafieldpart">
613
+ <input alt="Input box" class="form-control" type="text" name="eFtpXS5uYW1lLmZpcnN0" id="eFtpXS5uYW1lLmZpcnN0" required="">
614
+ </div>
615
+ </div>
616
+ <fieldset class="da-button-set da-field-buttons">
617
+ <legend class="visually-hidden">Press one of the following buttons:</legend>
618
+ <button class="btn btn-da btn-primary" id="da-continue-button" type="submit">Continue</button>
619
+ </fieldset>
620
+ <input type="hidden" name="csrf_token" value="ImNlMGRmOWY5NmQ4MGRjMjI0ZDQyNGFjMWRiYjdiODAzOTk2ZThhY2Ui.Zb7fVw.D9gMWkXreNYpd_eKkSXwMUybSZ4">
621
+ <input type="hidden" name="_event" value="WyJwcm94eV9saXN0WzFdLm5hbWUuZmlyc3QiXQ">
622
+ <input type="hidden" name="_question_name" value="ID proxy vars">
623
+ <input type="hidden" name="_tracker" value="7">
624
+ <input type="hidden" name="_datatypes" value="eyJlRnRwWFM1dVlXMWxMbVpwY25OMCI6ICJ0ZXh0In0">
625
+ <input type="hidden" name="_visible" value="">
626
+ <input type="hidden" name="_varnames" value="eyJYMlpwWld4a1h6QSI6ICJlRnRwWFM1dVlXMWxMbVpwY25OMCJ9">
627
+ </form>
628
+ </div>`;
629
+
568
630
 
569
631
  // ============================
570
632
  // Signature
@@ -107,12 +107,12 @@ matches.proxies_xi = [
107
107
  // Multiple proxies by the same name are on the list (because of a loop)
108
108
  // x[i].name.first
109
109
  matches.proxies_multi = [
110
- [{"trigger":"proxy_list[1].name.first","var":"x[i].name.first","value":"Firstname"}],
110
+ [{"trigger":"proxy_list[1].name.first","var":"x[i].name.first","value":"2 Firstname"}],
111
111
  []
112
112
  ];
113
113
 
114
114
  // Missing table row for second loop
115
- matches.proxies_missing_loop = [
115
+ matches.proxies_missing_table_row = [
116
116
  [],
117
117
  []
118
118
  ];
@@ -123,6 +123,24 @@ matches.proxies_missing_trigger = [
123
123
  []
124
124
  ];
125
125
 
126
+ // Proxy substitutes (no trigger) i = 0
127
+ matches.proxy_substitution_i_is_0 = [
128
+ [{"trigger": "","var": "proxy_list[0].name.first","value":"Firstname"}],
129
+ []
130
+ ];
131
+
132
+ // Proxy substitutes (no trigger) i = 1 (3 loops possible)
133
+ matches.proxy_substitution_i_is_1 = [
134
+ [{"trigger": "","var":"proxy_list[1].name.first","value":"2 Firstname"}],
135
+ []
136
+ ];
137
+
138
+ // Proxy substitution (no trigger) missing table row for second loop (i = 1)
139
+ matches.proxy_substitution_missing_table_row = [
140
+ [],
141
+ []
142
+ ];
143
+
126
144
 
127
145
  // ============================
128
146
  // Signature
@@ -139,16 +139,16 @@ tables.proxies_xi = [
139
139
  { "trigger": "proxy_list[0].name.first", "var": "x[i].name.first", "value": "Firstname", },
140
140
  ];
141
141
 
142
- // Multiple proxies by the same name are on the list (because of a loop)
142
+ // Multiple proxies by the same name are on the list because of a loop (3 loops possible)
143
143
  // x[i].name.first
144
144
  tables.proxies_multi = [
145
- { "trigger": "proxy_list[0].name.first", "var": "x[i].name.first", "value": "Firstname", },
146
- { "trigger": "proxy_list[1].name.first", "var": "x[i].name.first", "value": "Firstname", },
147
- { "trigger": "proxy_list[2].name.first", "var": "x[i].name.first", "value": "Firstname", },
145
+ { "trigger": "proxy_list[0].name.first", "var": "x[i].name.first", "value": "1 Firstname", },
146
+ { "trigger": "proxy_list[1].name.first", "var": "x[i].name.first", "value": "2 Firstname", },
147
+ { "trigger": "proxy_list[2].name.first", "var": "x[i].name.first", "value": "3 Firstname", },
148
148
  ];
149
149
 
150
150
  // Missing table row for second loop
151
- tables.proxies_missing_loop = [
151
+ tables.proxies_missing_table_row = [
152
152
  { "trigger": "proxy_list[0].name.first", "var": "x[i].name.first", "value": "Firstname", "original": { "trigger": "proxy_list[0].name.first", "var": "x[i].name.first", "value": "Firstname" } },
153
153
  ];
154
154
 
@@ -157,6 +157,23 @@ tables.proxies_missing_trigger = [
157
157
  { "trigger": "", "var": "x[i].name.first", "value": "Firstname", "original": { "trigger": "", "var": "x[i].name.first", "value": "Firstname" }},
158
158
  ];
159
159
 
160
+ // Proxy substitutes (no trigger) i = 0
161
+ tables.proxy_substitution_i_is_0 = [
162
+ { "trigger": "", "var": "proxy_list[0].name.first", "value": "Firstname", },
163
+ ];
164
+
165
+ // Proxy substitutes (no trigger) i = 1 (3 loops possible)
166
+ tables.proxy_substitution_i_is_1 = [
167
+ { "trigger": "", "var": "proxy_list[0].name.first", "value": "1 Firstname", },
168
+ { "trigger": "", "var": "proxy_list[1].name.first", "value": "2 Firstname", },
169
+ { "trigger": "", "var": "proxy_list[2].name.first", "value": "3 Firstname", },
170
+ ];
171
+
172
+ // Proxy substitution (no trigger) missing table row for second loop (i = 1)
173
+ tables.proxy_substitution_missing_table_row = [
174
+ { "trigger": "", "var": "proxy_list[0].name.first", "value": "Firstname", "original": { "trigger": "", "var": "proxy_list[0].name.first", "value": "Firstname" } },
175
+ ];
176
+
160
177
 
161
178
  // ============================
162
179
  // Signature