@suffolklitlab/alkiln 5.8.1 → 5.9.0-feat-url1
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.
- package/.github/workflows/github_server.yml +11 -7
- package/.github/workflows/playground.yml +2 -2
- package/CHANGELOG.md +17 -0
- package/docs/expected_inernal_results.md +15 -0
- package/lib/cucumber_8_shim.js +1 -0
- package/lib/scope.js +270 -139
- package/lib/steps.js +191 -34
- package/lib/utils/log.js +16 -5
- package/lib/utils/session_vars.js +1 -1
- package/package.json +1 -1
- package/tests/unit_tests/fields.fixtures.js +477 -103
- package/tests/unit_tests/getAllFields.test.js +22 -8
- package/tests/unit_tests/getMatchingRows.test.js +44 -2
- package/tests/unit_tests/html.fixtures.js +66 -4
- package/tests/unit_tests/matches.fixtures.js +20 -2
- package/tests/unit_tests/tables.fixtures.js +22 -5
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 }"
|
|
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 = `
|
|
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
|
|
468
|
-
+ `
|
|
469
|
-
+ `error will
|
|
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 );
|
|
@@ -573,10 +573,7 @@ module.exports = {
|
|
|
573
573
|
axe_path = await scope.getA11yPath( scope );
|
|
574
574
|
fs.writeFileSync( axe_path, JSON.stringify( axe_result, null, 2 ));
|
|
575
575
|
let msg = `Found potential accessibility issues on the ${ scope.page_id } screen. Details in ${ axe_path }.`;
|
|
576
|
-
reports.addToReport( scope, {
|
|
577
|
-
type: `error`,
|
|
578
|
-
value: msg
|
|
579
|
-
});
|
|
576
|
+
reports.addToReport( scope, { type: `error`, value: msg });
|
|
580
577
|
} else {
|
|
581
578
|
reports.addToReport(scope, { type: `info`, value: `Accessibility on ${ scope.page_id } passed!`})
|
|
582
579
|
}
|
|
@@ -629,7 +626,7 @@ module.exports = {
|
|
|
629
626
|
|
|
630
627
|
reports.addToReport(scope,{
|
|
631
628
|
type: `row`,
|
|
632
|
-
value: `Trying to load the interview at "${interview_url}"`
|
|
629
|
+
value: `Trying to load the interview at "${ interview_url }"`
|
|
633
630
|
});
|
|
634
631
|
|
|
635
632
|
if ( !scope.page ) { scope.page = await scope.browser.newPage(); }
|
|
@@ -644,8 +641,9 @@ module.exports = {
|
|
|
644
641
|
// If the page didn't load because it took to long to load
|
|
645
642
|
if ( error.name === `TimeoutError` ) {
|
|
646
643
|
|
|
647
|
-
let non_reload_report_msg = `Timeout error occurred when ALKiln tried to go to the interview
|
|
644
|
+
let non_reload_report_msg = `Timeout error occurred when ALKiln tried to go to the interview at ${ interview_url }`;
|
|
648
645
|
await scope.handle_page_timeout_error( scope, { non_reload_report_msg, error });
|
|
646
|
+
// TODO: Should this throw to avoid multiple error messages?
|
|
649
647
|
|
|
650
648
|
// If the page didn't load because of any other reason
|
|
651
649
|
} else {
|
|
@@ -653,7 +651,7 @@ module.exports = {
|
|
|
653
651
|
// Throw any non-timeout error
|
|
654
652
|
reports.addToReport( scope, {
|
|
655
653
|
type: `error`,
|
|
656
|
-
value: `Error occurred when ALKiln tried to go to the interview
|
|
654
|
+
value: `Error occurred when ALKiln tried to go to the interview at ${ interview_url }`
|
|
657
655
|
});
|
|
658
656
|
throw( error );
|
|
659
657
|
} // ends if error is timeout error
|
|
@@ -661,10 +659,12 @@ module.exports = {
|
|
|
661
659
|
} // ends try/catch
|
|
662
660
|
|
|
663
661
|
let load_result = await scope.getLoadData( scope );
|
|
664
|
-
//
|
|
662
|
+
// da page invalidation errors or timeout will show an error to the developer at the end
|
|
665
663
|
if ( load_result.error ) {
|
|
666
|
-
reports.addToReport(scope, { type: `error`, value: `On final attempt to load interview, got "${ load_result.error }"` });
|
|
664
|
+
reports.addToReport(scope, { type: `error`, value: `On final attempt to load interview at "${ interview_url }", got "${ load_result.error }"` });
|
|
667
665
|
expect( load_result.error ).to.equal( '' );
|
|
666
|
+
} else {
|
|
667
|
+
reports.addToReport(scope, { type: `info`, value: `Successfully found the interview at "${ interview_url }"` });
|
|
668
668
|
}
|
|
669
669
|
|
|
670
670
|
return scope.page;
|
|
@@ -696,6 +696,11 @@ module.exports = {
|
|
|
696
696
|
|
|
697
697
|
// or if they cause some other kind of error
|
|
698
698
|
} else {
|
|
699
|
+
reports.addToReport(scope, {
|
|
700
|
+
type: `error`,
|
|
701
|
+
value: `ALKiln got an error when it tried to load the interview page`,
|
|
702
|
+
data: error
|
|
703
|
+
});
|
|
699
704
|
throw error;
|
|
700
705
|
} // ends if error.name.includes(`TimeoutError`)
|
|
701
706
|
}); // ends Promise.race()
|
|
@@ -730,7 +735,7 @@ module.exports = {
|
|
|
730
735
|
* normalizeTable internally.
|
|
731
736
|
*/
|
|
732
737
|
|
|
733
|
-
if (!var_data && !
|
|
738
|
+
if (!var_data && !raw_var_data) {
|
|
734
739
|
let no_data_msg = `ALKiln Internal error: normalizeTable called w/o var_data or raw_var_data:` +
|
|
735
740
|
` open an issue at https://github.com/SuffolkLITLab/ALKiln/issues/new if you see this!`;
|
|
736
741
|
reports.addToReport( scope, { type: `error`, value: no_data_msg });
|
|
@@ -739,39 +744,52 @@ module.exports = {
|
|
|
739
744
|
|
|
740
745
|
// Always use the raw var data if present; it has the full first row
|
|
741
746
|
if (raw_var_data) {
|
|
747
|
+
|
|
742
748
|
var_data = raw_var_data.hashes();
|
|
743
|
-
|
|
749
|
+
// If this table doesn't have headers
|
|
750
|
+
if (!(`var` in var_data[0]) || !(`value` in var_data[0])) {
|
|
744
751
|
let raw_array = raw_var_data.raw();
|
|
745
752
|
var_data = [];
|
|
746
753
|
for (let row_raw of raw_array) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
754
|
+
|
|
755
|
+
// Wrong number of columns error
|
|
756
|
+
if (row_raw.length < 2 || row_raw.length > 3) {
|
|
757
|
+
let more_or_less = `more`;
|
|
758
|
+
if ( row_raw.length > 3 ) { more_or_less = `less`; }
|
|
759
|
+
let bad_table_msg = `Your Story Table should have ${ more_or_less } than`
|
|
760
|
+
+ ` ${ row_raw.length } columns. Take another look at Story Table documentation at`
|
|
761
|
+
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#story-tables.`
|
|
751
762
|
reports.addToReport( scope, { type: `error`, value: bad_table_msg})
|
|
752
763
|
throw Error(bad_table_msg);
|
|
753
|
-
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (row_raw.length == 2) {
|
|
754
767
|
var_data.push({var: row_raw[0], value: row_raw[1]});
|
|
755
768
|
} else {
|
|
756
769
|
var_data.push({var: row_raw[0], value: row_raw[1], trigger: row_raw[2]});
|
|
757
770
|
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
771
|
+
} // ends for every row in table
|
|
772
|
+
} // ends if this table doesn't have headers
|
|
773
|
+
|
|
774
|
+
} // ends if we have access to the full raw data
|
|
761
775
|
|
|
762
776
|
// Support tables with no 'trigger' column
|
|
763
777
|
let supported_table = [];
|
|
778
|
+
|
|
764
779
|
for ( let row of var_data ) {
|
|
765
780
|
// See tests > unit_tests > tables.fixtures.js > tables.old_to_current_formatting
|
|
766
781
|
let actual_var_value = row.value;
|
|
767
782
|
if (is_secret_var(row)) {
|
|
768
|
-
actual_var_value = process.env[row.flags.secret_name];
|
|
783
|
+
actual_var_value = process.env[ row.flags.secret_name ];
|
|
769
784
|
if ( actual_var_value === undefined ) {
|
|
770
|
-
let not_in_env_msg = `
|
|
771
|
-
|
|
772
|
-
|
|
785
|
+
let not_in_env_msg = `ALKiln could not find the GitHub secret "${ row.flags.secret_name }".`
|
|
786
|
+
+ ` Check for a typo and check your workflow file.`
|
|
787
|
+
+ ` Read more about using GitHub secrets in your workflow file at`
|
|
788
|
+
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#github-secrets.`
|
|
789
|
+
reports.addToReport( scope, { type: `error`, value: not_in_env_msg });
|
|
790
|
+
throw ReferenceError(not_in_env_msg);
|
|
773
791
|
}
|
|
774
|
-
}
|
|
792
|
+
} // ends if is secret var
|
|
775
793
|
|
|
776
794
|
let result = {
|
|
777
795
|
// NOTE: mutating frozen objects will fail silently, so don't mutate this attribute
|
|
@@ -780,9 +798,9 @@ module.exports = {
|
|
|
780
798
|
// Give fake data if needed to prevent some kind of hidden input field from being found
|
|
781
799
|
// See bug at https://github.com/plocket/docassemble-cucumber/issues/79
|
|
782
800
|
// May be able to remove this once converted to `getAllFields`
|
|
783
|
-
var: row.var ||
|
|
801
|
+
var: row.var || `alkiln_no_var_name`,
|
|
784
802
|
value: actual_var_value,
|
|
785
|
-
trigger: row.trigger ||
|
|
803
|
+
trigger: row.trigger || ``,
|
|
786
804
|
// This will track how many times total the row or its connected
|
|
787
805
|
// artificial rows are used. Artificial rows are only added for some rows.
|
|
788
806
|
times_used: 0,
|
|
@@ -791,11 +809,12 @@ module.exports = {
|
|
|
791
809
|
result.used_for[ result.var ] = 0;
|
|
792
810
|
|
|
793
811
|
supported_table.push( result );
|
|
794
|
-
}
|
|
812
|
+
} // ends for every row
|
|
795
813
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
814
|
+
log.debug({
|
|
815
|
+
type: `info`,
|
|
816
|
+
pre: `scope.normalizedTable():\n ${JSON.stringify( supported_table )}`
|
|
817
|
+
})
|
|
799
818
|
|
|
800
819
|
return supported_table;
|
|
801
820
|
}, // Ends scope.normalizeTable()
|
|
@@ -815,17 +834,22 @@ module.exports = {
|
|
|
815
834
|
// This is the new trigger var element that developers will have to add to their interviews.
|
|
816
835
|
// AssemblyLine will add it automatically unless someone overrides the values in their own interview.
|
|
817
836
|
// See the report message to the dev below for a link to an example.
|
|
818
|
-
let trigger_elem = $( `#trigger` );
|
|
819
|
-
if (
|
|
820
|
-
let encoded_trigger_var = $( trigger_elem ).data( `variable` )
|
|
821
|
-
if ( !encoded_trigger_var ) {
|
|
822
|
-
// TODO: Add documentation and then link to it in this message.
|
|
823
|
-
reports.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.` });
|
|
824
|
-
encoded_trigger_var = toBase64( `None` );
|
|
825
|
-
}
|
|
837
|
+
let $trigger_elem = $( `#trigger` );
|
|
838
|
+
if ( !$trigger_elem.length ) { $trigger_elem = $( `#sought_variable` ); } // Backwards compatibility
|
|
839
|
+
let encoded_trigger_var = $( $trigger_elem ).data( `variable` ) || ``;
|
|
826
840
|
let trigger = fromBase64( encoded_trigger_var );
|
|
827
841
|
|
|
828
|
-
|
|
842
|
+
let $proxies_node = $( `#alkiln_proxy_var_values` );
|
|
843
|
+
let proxies = {};
|
|
844
|
+
// If possible, get values of `x` and `i` that the dev put on the page
|
|
845
|
+
if ( $proxies_node.length ) {
|
|
846
|
+
// Empty strings are fine since that means that proxy is absent and won't come into play.
|
|
847
|
+
proxies.x = fromBase64( $proxies_node.data( `generic_object` ) );
|
|
848
|
+
for ( let index_var of `ijklmn`.split(``) ) {
|
|
849
|
+
proxies[index_var] = fromBase64( $proxies_node.data( `index_var_${ index_var }` ) );
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
829
853
|
// Some `show if` fields have `names` like '_field_n' instead of their var name. Ex: _field_1
|
|
830
854
|
let field_like_names = await scope.getFieldNamesDict( scope, { $: $ });
|
|
831
855
|
|
|
@@ -845,62 +869,62 @@ module.exports = {
|
|
|
845
869
|
};
|
|
846
870
|
if ( node.attribs.type ) { field_data.type = node.attribs.type; }
|
|
847
871
|
|
|
872
|
+
// Often used for the variable name
|
|
873
|
+
let not_decoded_name = $node.attr( `name` );
|
|
874
|
+
|
|
875
|
+
// `<input type="hidden" name="_save_as" value="c2lnbmF0dXJlXzE">`
|
|
848
876
|
// canvas elements are the only field on a signature page
|
|
849
877
|
if ( $(`#dasigpage`).length > 0 && $(`#dasigpage`)[0].name ) {
|
|
850
878
|
field_data.selector = `#daquestion canvas`;
|
|
851
879
|
field_data.tag = `canvas`;
|
|
852
|
-
|
|
853
|
-
var: trigger,
|
|
854
|
-
value: '/sign',
|
|
855
|
-
}];
|
|
856
|
-
fields.push( field_data );
|
|
857
|
-
continue;
|
|
880
|
+
not_decoded_name = $( `input[name="_save_as"]` ).attr( `value` );
|
|
858
881
|
}
|
|
859
882
|
|
|
860
883
|
// The selector we'll use to set the DOM node's value later
|
|
861
|
-
field_data.selector
|
|
884
|
+
if ( !field_data.selector ) {
|
|
885
|
+
field_data.selector = await scope.getFieldSelector( scope, { $, node });
|
|
886
|
+
}
|
|
862
887
|
|
|
863
|
-
// Often used in the variable name
|
|
864
|
-
let encoded_name = $node.attr( `name` );
|
|
865
888
|
// Action buttons keep their var name in a unique place
|
|
889
|
+
// TODO: add examples of the hrefs
|
|
866
890
|
if ( $( node ).hasClass( 'daquestionactionbutton' )) {
|
|
867
891
|
let href = node.attribs.href;
|
|
868
892
|
let match = href.match( /action=([^&]*)/ );
|
|
869
893
|
if ( match ) {
|
|
870
|
-
|
|
894
|
+
not_decoded_name = match[1];
|
|
871
895
|
} else {
|
|
872
896
|
// Some action buttons just use urls instead of da actions
|
|
873
|
-
//
|
|
874
|
-
// TODO: This only replaces the chars at the start of the string. Is that
|
|
875
|
-
|
|
897
|
+
// This means the first guess of the name should be the not_decoded_name
|
|
898
|
+
// TODO: This only replaces the chars at the start of the string. Is that
|
|
899
|
+
// intentional? Does this `.replace()` need the `g` flag?
|
|
900
|
+
not_decoded_name = href.replace(/^[A-Za-z0-9]*/, '_');
|
|
876
901
|
}
|
|
877
902
|
}
|
|
878
903
|
|
|
879
|
-
if ( !
|
|
904
|
+
if ( !not_decoded_name ) {
|
|
880
905
|
// Some fields, like comboboxes, have a weird var name setup
|
|
881
|
-
|
|
906
|
+
not_decoded_name = $node.attr( `id` );
|
|
882
907
|
}
|
|
883
908
|
|
|
884
909
|
// ====================
|
|
885
910
|
// Table rows
|
|
886
911
|
// ====================
|
|
887
|
-
//
|
|
888
|
-
let
|
|
889
|
-
encoded: encoded_name,
|
|
890
|
-
field_like_names,
|
|
891
|
-
nota: false, // none of the above (detected and overwritten later)
|
|
892
|
-
});
|
|
893
|
-
|
|
912
|
+
// "None of the above" checkbox/object radio options don't contain their variable's name
|
|
913
|
+
let is_none_of_the_above = false;
|
|
894
914
|
if ( $node.hasClass('danota-checkbox') || $node.parents(`.da-field-container-datatype-object_radio`).name ) {
|
|
895
|
-
|
|
915
|
+
is_none_of_the_above = true;
|
|
896
916
|
let first_sib = $(node.parent).find( 'input' )[0];
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
field_like_names,
|
|
900
|
-
nota: true, // none of the above
|
|
901
|
-
});
|
|
917
|
+
not_decoded_name = first_sib.attribs.name;
|
|
918
|
+
// TODO: Do we need daaota as well? ("all of the above")
|
|
902
919
|
}
|
|
903
920
|
|
|
921
|
+
let var_names = await scope.getPossibleDecodedNames( scope, {
|
|
922
|
+
not_decoded: not_decoded_name,
|
|
923
|
+
field_like_names,
|
|
924
|
+
is_none_of_the_above: is_none_of_the_above,
|
|
925
|
+
proxies,
|
|
926
|
+
});
|
|
927
|
+
|
|
904
928
|
// button, radio, checkbox yesno, and maybe others need the value of the `value` attribute
|
|
905
929
|
let values = [];
|
|
906
930
|
if ( node.name === `select` ) {
|
|
@@ -926,8 +950,6 @@ module.exports = {
|
|
|
926
950
|
return await scope.page.evaluate(async function ( elem ) {
|
|
927
951
|
return await get_interview_variables();
|
|
928
952
|
});
|
|
929
|
-
|
|
930
|
-
// return data;
|
|
931
953
|
}, // Ends scope.getPageJSON()
|
|
932
954
|
|
|
933
955
|
getFieldSelector: async function ( scope, { $, node }) {
|
|
@@ -978,10 +1000,11 @@ module.exports = {
|
|
|
978
1000
|
// Ignore blanks, and values that certainly aren't base64 encoded like True and False
|
|
979
1001
|
// Modified from https://stackoverflow.com/a/8571649/11416267
|
|
980
1002
|
// DA doesn't use `==` to pad base 64
|
|
1003
|
+
// TODO: How are 'None', 'True', and 'False' being handled?
|
|
981
1004
|
if (value !== 'None' && value !== 'True' && value !== 'False'
|
|
982
1005
|
&& value.match(da_base64_regex)) {
|
|
983
1006
|
let value_decoded = fromBase64(value);
|
|
984
|
-
let values_decoded = scope.decodeFully( scope, {expr_str: value_decoded });
|
|
1007
|
+
let values_decoded = scope.decodeFully( scope, { expr_str: value_decoded });
|
|
985
1008
|
for (let possible_val of values_decoded) {
|
|
986
1009
|
possible_table_rows.push({var: var_name, value: possible_val, });
|
|
987
1010
|
}
|
|
@@ -1008,12 +1031,20 @@ module.exports = {
|
|
|
1008
1031
|
*/
|
|
1009
1032
|
if ( !$('input[name="_varnames"]')[0] ) { return {}; }
|
|
1010
1033
|
|
|
1034
|
+
// TODO: Why are we docoding the field names here? We could decode them later, right?
|
|
1035
|
+
// In `getPossibleDecodedNames` we could do:
|
|
1036
|
+
// if ( field_like_original_names[ not_decoded ] ) {
|
|
1037
|
+
// still_encoded = field_like_names[ not_decoded ];
|
|
1038
|
+
// decode( still_encoded )
|
|
1039
|
+
// }
|
|
1040
|
+
|
|
1011
1041
|
let field_like_names = {};
|
|
1012
1042
|
let field_like_names_encoded = $('input[name="_varnames"]')[0].attribs.value;
|
|
1013
1043
|
let field_like_names_str = fromBase64( field_like_names_encoded );
|
|
1014
1044
|
let field_like_names_json = JSON.parse( field_like_names_str );
|
|
1015
1045
|
for ( let field_like_key in field_like_names_json ) {
|
|
1016
1046
|
let field_like_DOM_name = fromBase64( field_like_key );
|
|
1047
|
+
// Even with `multiselect`, field names are only encoded once
|
|
1017
1048
|
let field_like_var_name = fromBase64( field_like_names_json[ field_like_key ] );
|
|
1018
1049
|
field_like_names[ field_like_DOM_name ] = field_like_var_name;
|
|
1019
1050
|
}
|
|
@@ -1021,8 +1052,13 @@ module.exports = {
|
|
|
1021
1052
|
return field_like_names;
|
|
1022
1053
|
}, // Ends scope.getFieldNamesDict()
|
|
1023
1054
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1055
|
+
getPossibleDecodedNames: async function ( scope, {
|
|
1056
|
+
not_decoded,
|
|
1057
|
+
field_like_names={},
|
|
1058
|
+
is_none_of_the_above=false,
|
|
1059
|
+
proxies={}
|
|
1060
|
+
}) {
|
|
1061
|
+
/** Given a potentially base64 encoded string and a map of `_field_n` to var names, return
|
|
1026
1062
|
* the decoded possible var names and, sometimes, values that the
|
|
1027
1063
|
* encoded string could be. Only some node names actually use `_field_n`.
|
|
1028
1064
|
*
|
|
@@ -1040,41 +1076,69 @@ module.exports = {
|
|
|
1040
1076
|
* TODO: We'll probably eventually have to search for `[B'encoded']` and such
|
|
1041
1077
|
* iteratively to get all of them. Hasn't cropped up yet, though.
|
|
1042
1078
|
*
|
|
1043
|
-
* @param
|
|
1079
|
+
* @param not_decoded {str} - potentially base64 encoded string from the node
|
|
1044
1080
|
* @param field_like_names {obj} - e.g. `{ _field_0: 'some_checkbox_var_name' }`
|
|
1045
|
-
* @param
|
|
1081
|
+
* @param is_none_of_the_above {bool} - is the field a 'none of the above' field?
|
|
1082
|
+
* @param proxies {obj} - e.g. `{ x: str, i: str, j: str, ... n: str }`. Empty strings
|
|
1083
|
+
* are fine since that means that proxy is absent and won't come into play.
|
|
1084
|
+
*/
|
|
1085
|
+
|
|
1046
1086
|
// E.g. continue buttons that don't set a variable
|
|
1047
|
-
if (
|
|
1087
|
+
if ( not_decoded === undefined ) { return []; }
|
|
1088
|
+
// Not sure it's necessary to have both with and without proxies here
|
|
1089
|
+
let guesses = scope.with_and_without_proxies( scope, {
|
|
1090
|
+
var_name: `${ not_decoded }`,
|
|
1091
|
+
proxies,
|
|
1092
|
+
});
|
|
1048
1093
|
|
|
1049
1094
|
// Names can be encoded multiple times, so start peeling the layers
|
|
1050
|
-
let
|
|
1051
|
-
// If this is of the format `_field_n`, we need to get the actual matching var name
|
|
1052
|
-
if ( field_like_names[
|
|
1053
|
-
|
|
1095
|
+
let decoded_once = fromBase64( not_decoded );
|
|
1096
|
+
// If this is of the format `_field_n`, we need to get the actual matching var name
|
|
1097
|
+
if ( field_like_names[ decoded_once ] ) {
|
|
1098
|
+
decoded_once = field_like_names[ decoded_once ];
|
|
1054
1099
|
}
|
|
1055
1100
|
|
|
1056
1101
|
try {
|
|
1057
|
-
|
|
1058
|
-
|
|
1102
|
+
// We think this never has proxy vars
|
|
1103
|
+
let action_button_json = JSON.parse( decoded_once );
|
|
1104
|
+
if ( action_button_json.action ) { decoded_once = action_button_json.action }
|
|
1059
1105
|
} catch ( error ) {
|
|
1060
1106
|
// Things that are not action buttons can't be parsed this way.
|
|
1061
1107
|
// A special error for action buttons requires passing the node in here too, which is a lot to pass in.
|
|
1062
1108
|
// TODO: Should we move this back into `getAllFields()`?
|
|
1063
1109
|
}
|
|
1064
1110
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1111
|
+
// Include `not_decoded` since the name might not be encoded
|
|
1112
|
+
// TODO: Should we try to replace proxies in `not_decoded`. I can't
|
|
1113
|
+
// think of an occasion where that would be necessary.
|
|
1114
|
+
guesses = guesses.concat( scope.decodeFully( scope, {
|
|
1115
|
+
expr_str: decoded_once,
|
|
1116
|
+
field_like_names,
|
|
1117
|
+
is_none_of_the_above,
|
|
1118
|
+
proxies
|
|
1119
|
+
})
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
return guesses;
|
|
1123
|
+
}, // Ends scope.getPossibleDecodedNames()
|
|
1124
|
+
|
|
1125
|
+
decodeFully: function ( scope, {
|
|
1126
|
+
expr_str,
|
|
1127
|
+
field_like_names = {},
|
|
1128
|
+
is_none_of_the_above=false,
|
|
1129
|
+
proxies={}
|
|
1130
|
+
}) {
|
|
1131
|
+
/**
|
|
1070
1132
|
* Given a DA variable or value name, make sure it's fully decoded from base64.
|
|
1071
|
-
* @param
|
|
1133
|
+
* @param expr_str {str} - a python variable string, potentially partially base64 encoded
|
|
1072
1134
|
* Some examples:
|
|
1073
1135
|
* - my_variable
|
|
1074
1136
|
* - checkboxes_choices[B'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
|
|
1075
1137
|
* - foo[bar][B'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
|
|
1076
1138
|
* @param field_like_names {obj} - e.g. `{ _field_0: 'some_checkbox_var_name' }`
|
|
1077
|
-
* @param
|
|
1139
|
+
* @param is_none_of_the_above {bool} - is the field a 'none of the above' field?
|
|
1140
|
+
* @param proxies {obj} - e.g. `{ x: str, i: str, j: str, ... n: str }`. Empty strings
|
|
1141
|
+
* are fine since that means that proxy is absent and won't come into play.
|
|
1078
1142
|
*/
|
|
1079
1143
|
// Some fields have a key hidden inside them.
|
|
1080
1144
|
// This gets the variable name before the final brackets, and the likely
|
|
@@ -1094,22 +1158,37 @@ module.exports = {
|
|
|
1094
1158
|
if ( before_and_in_brackets ) {
|
|
1095
1159
|
let key_or_name = before_and_in_brackets[1];
|
|
1096
1160
|
// If this is of the format `_field_n`, we need to get the actual matching var name.
|
|
1097
|
-
let var_name = field_like_names
|
|
1161
|
+
let var_name = field_like_names[ key_or_name ] || key_or_name;
|
|
1098
1162
|
|
|
1099
1163
|
let dictionary_key = before_and_in_brackets[2];
|
|
1100
1164
|
|
|
1101
1165
|
// Our own marker for a 'none of the above' checkbox choice
|
|
1102
|
-
if (
|
|
1103
|
-
possible.push( `${ var_name }['None']` );
|
|
1166
|
+
if ( is_none_of_the_above ) {
|
|
1167
|
+
// possible.push( `${ var_name }['None']` );
|
|
1168
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1169
|
+
var_name: `${ var_name }['None']`,
|
|
1170
|
+
proxies,
|
|
1171
|
+
}));
|
|
1104
1172
|
} else {
|
|
1173
|
+
|
|
1174
|
+
// TODO: Just use try/catches?
|
|
1175
|
+
|
|
1105
1176
|
if (dictionary_key.match(da_base64_regex)) {
|
|
1106
1177
|
// Get the choice name. E.g. option_1, option_2
|
|
1107
1178
|
let dictionary_key_decoded = fromBase64( dictionary_key );
|
|
1108
|
-
possible.push( `${ var_name }['${ dictionary_key_decoded }']` );
|
|
1179
|
+
// possible.push( `${ var_name }['${ dictionary_key_decoded }']` );
|
|
1180
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1181
|
+
var_name: `${ var_name }['${ dictionary_key_decoded }']`,
|
|
1182
|
+
proxies,
|
|
1183
|
+
}));
|
|
1109
1184
|
if (dictionary_key_decoded.match(da_base64_regex)) {
|
|
1110
1185
|
try {
|
|
1111
|
-
// The part inside `[B'encoded']` could need one more decoding.
|
|
1112
|
-
possible.push(`${ var_name }['${ fromBase64( dictionary_key_decoded ) }']`);
|
|
1186
|
+
// The part inside `[B|R|O'encoded']` could need one more decoding.
|
|
1187
|
+
// possible.push(`${ var_name }['${ fromBase64( dictionary_key_decoded ) }']`);
|
|
1188
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1189
|
+
var_name: `${ var_name }['${ fromBase64( dictionary_key_decoded ) }']`,
|
|
1190
|
+
proxies,
|
|
1191
|
+
}));
|
|
1113
1192
|
} catch ( err ) {} // It wasn't double encoded. Do nothing.
|
|
1114
1193
|
}
|
|
1115
1194
|
}
|
|
@@ -1118,18 +1197,65 @@ module.exports = {
|
|
|
1118
1197
|
// =====================
|
|
1119
1198
|
// If bracket regex didn't match, just use var name
|
|
1120
1199
|
} else {
|
|
1121
|
-
possible.
|
|
1200
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1201
|
+
var_name: `${ expr_str }`,
|
|
1202
|
+
proxies,
|
|
1203
|
+
}) );
|
|
1122
1204
|
}
|
|
1123
1205
|
return possible;
|
|
1124
1206
|
},
|
|
1125
1207
|
|
|
1208
|
+
with_and_without_proxies: function ( scope, { var_name, proxies={} }) {
|
|
1209
|
+
/** Given a variable or value's possible name and a map of values for
|
|
1210
|
+
* each proxy variable, return a list of either one or two strings.
|
|
1211
|
+
* It always includes the given variable name. If proxies were found
|
|
1212
|
+
* and replaced, the list also includes a version of the variable
|
|
1213
|
+
* name in which the proxies have been replaced by their values.
|
|
1214
|
+
*
|
|
1215
|
+
* Proxies are generic objects (x) or index variables (i, j, k, l, m, n).
|
|
1216
|
+
*
|
|
1217
|
+
* @param { string } var_name - The possible name of a variable or value
|
|
1218
|
+
* @param { Object } proxies - A map of proxy variables to their values on
|
|
1219
|
+
* the HTML page, if they're there.
|
|
1220
|
+
*
|
|
1221
|
+
* @returns {Array<string>} - An array of at least one string - var_name
|
|
1222
|
+
* itself. Possibly a second value where proxy variables in var_name
|
|
1223
|
+
* are replaced with their values (if the proxy variables are present
|
|
1224
|
+
* and if their values are passed in).
|
|
1225
|
+
* */
|
|
1226
|
+
// We could combine these regexes, but this may be easier to read
|
|
1227
|
+
let generic_obj_regex = /^(x)\b/;
|
|
1228
|
+
let index_var_regex = /(?<=\[)([ijklmn])(?=\])/g;
|
|
1229
|
+
// If proxies exist in the HTML, replace any proxies in the variables
|
|
1230
|
+
// with those values.
|
|
1231
|
+
let new_name = var_name;
|
|
1232
|
+
if ( proxies.x !== '' && proxies.x !== undefined ) {
|
|
1233
|
+
new_name = new_name.replace( generic_obj_regex, proxies.x );
|
|
1234
|
+
}
|
|
1235
|
+
new_name = new_name.replace( index_var_regex, function(match) {
|
|
1236
|
+
if ( proxies[ match ] === '' || proxies[ match ] === undefined ) {
|
|
1237
|
+
return match;
|
|
1238
|
+
} else {
|
|
1239
|
+
return proxies[ match ];
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
// The older versions of Story Table need to see "x", "i", etc.,
|
|
1244
|
+
// so keep that old name around.
|
|
1245
|
+
let unique_names = [ var_name ];
|
|
1246
|
+
// If there were proxies, add the new version of the name
|
|
1247
|
+
if ( new_name !== var_name ) { unique_names.push( new_name ); }
|
|
1248
|
+
|
|
1249
|
+
return unique_names;
|
|
1250
|
+
}, // Ends scope.with_and_without_proxies()
|
|
1251
|
+
|
|
1126
1252
|
getOptionValues: async function ( scope, { $select }) {
|
|
1127
1253
|
/* Returns all the option node values of the select node. */
|
|
1128
1254
|
let options = [];
|
|
1129
1255
|
let option_nodes = $select.find( `option` );
|
|
1130
1256
|
for ( let opt of option_nodes ) {
|
|
1131
1257
|
options.push( opt.attribs.value );
|
|
1132
|
-
// Dropdowns created w/ objects are base64 encoded; but it's handled in
|
|
1258
|
+
// Dropdowns created w/ objects are base64 encoded; but it's handled in getPossibleTableRowMatchers
|
|
1133
1259
|
}
|
|
1134
1260
|
return options;
|
|
1135
1261
|
}, // Ends scope.getOptionValues()
|
|
@@ -1137,11 +1263,11 @@ module.exports = {
|
|
|
1137
1263
|
|
|
1138
1264
|
// TODO: Maybe add `from_story_table` to the var data table (array...)
|
|
1139
1265
|
getMatchingRows: async function ( scope, { field, var_data }) {
|
|
1140
|
-
|
|
1266
|
+
/** Given var_data, a list of objects with variable data for
|
|
1141
1267
|
* setting fields, and an object for a DOM field, returns list of the
|
|
1142
1268
|
* rows that match the DOM field. That list of story table rows may be empty.
|
|
1143
1269
|
*
|
|
1144
|
-
* For notes on trigger var, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
1270
|
+
* For notes on trigger var, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger
|
|
1145
1271
|
*
|
|
1146
1272
|
* @param var_data {arr} - fields and the values they can be set to
|
|
1147
1273
|
* @param var_data[n].var {str} - name of variable to set
|
|
@@ -1205,6 +1331,7 @@ module.exports = {
|
|
|
1205
1331
|
// If the field uses proxy variables - x, i, j, etc, flag it. See "proxies" note.
|
|
1206
1332
|
uses_proxies = uses_proxies || guess.var.match( proxies_regex );
|
|
1207
1333
|
|
|
1334
|
+
// TODO: can we just use `uses_proxies` in here?
|
|
1208
1335
|
if ( table_row.trigger === scope.trigger_not_needed_flag ) {
|
|
1209
1336
|
trigger_might_be_needed = false;
|
|
1210
1337
|
}
|
|
@@ -1235,9 +1362,9 @@ module.exports = {
|
|
|
1235
1362
|
// field/page trigger name is needed but is missing.
|
|
1236
1363
|
if ( !page_trigger_name ) {
|
|
1237
1364
|
let page_trigger_warning = `A field on this page uses an index `
|
|
1238
|
-
+ `variable or a generic object, but the page
|
|
1239
|
-
+ `
|
|
1240
|
-
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
1365
|
+
+ `variable or a generic object, but the page is missing `
|
|
1366
|
+
+ `the ALKiln "trigger" variable's name. See `
|
|
1367
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#a-missing-trigger-variable.`;
|
|
1241
1368
|
reports.addToReport(scope, { type: `warning`, value: page_trigger_warning });
|
|
1242
1369
|
}
|
|
1243
1370
|
|
|
@@ -1247,18 +1374,18 @@ module.exports = {
|
|
|
1247
1374
|
|
|
1248
1375
|
if ( !table_row_trigger_name ) {
|
|
1249
1376
|
// var/row trigger name is (probably) needed but is missing. Caveat: If there's
|
|
1250
|
-
// only one page in the proxy var loop and there's only one row
|
|
1251
|
-
// not needed, but I'm not sure how we figure out all those
|
|
1252
|
-
// a useful message.
|
|
1377
|
+
// only one page in the proxy var loop and there's only one row (like the "I set the var"
|
|
1378
|
+
// Step), trigger name is not needed, but I'm not sure how we figure out all those
|
|
1379
|
+
// things here and log a useful message.
|
|
1253
1380
|
|
|
1254
1381
|
// First, make the row data more readable to humans.
|
|
1255
1382
|
let row_as_story_table = reports.convertToOriginalStoryTableRow( scope, { row_data: row_that_matches_this_field });
|
|
1256
|
-
let row_trigger_warning = `This
|
|
1383
|
+
let row_trigger_warning = `This Story Table row needs a value in the "trigger" `
|
|
1257
1384
|
+ `column, but it is empty. The test might get unexpected results. See `
|
|
1258
|
-
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
1259
|
-
+ `\nThe
|
|
1260
|
-
+ `\nThis field's data:\n${ JSON.stringify( field.guesses )}`;
|
|
1385
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1386
|
+
+ `\nThe rows:\n${ row_as_story_table }`;
|
|
1261
1387
|
reports.addToReport(scope, { type: `warning`, value: row_trigger_warning });
|
|
1388
|
+
|
|
1262
1389
|
rows_without_a_trigger_name.push( row_that_matches_this_field );
|
|
1263
1390
|
} else {
|
|
1264
1391
|
let triggers_match = await scope.matchNeutralizedQuotes( scope, { value1: page_trigger_name, value2: table_row_trigger_name });
|
|
@@ -1278,7 +1405,7 @@ module.exports = {
|
|
|
1278
1405
|
}
|
|
1279
1406
|
|
|
1280
1407
|
// Warn the user if all matching rows had trigger names that conflicted.
|
|
1281
|
-
// Missing trigger names don't count as conflicting.
|
|
1408
|
+
// Missing trigger names don't count as conflicting. [Did this mean missing in the table or in the page?]
|
|
1282
1409
|
if ( final_matching_rows.length === 0 ) {
|
|
1283
1410
|
|
|
1284
1411
|
// First, make the row data more readable to humans.
|
|
@@ -1288,12 +1415,12 @@ module.exports = {
|
|
|
1288
1415
|
conflicts_as_story_table_rows.push( table_str );
|
|
1289
1416
|
}
|
|
1290
1417
|
|
|
1291
|
-
let all_trigger_names_conflict_msg =
|
|
1292
|
-
+
|
|
1293
|
-
+ `
|
|
1294
|
-
+ `
|
|
1295
|
-
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
1296
|
-
+ `\nThe
|
|
1418
|
+
let all_trigger_names_conflict_msg = `${ rows_that_match_field_names.length } `
|
|
1419
|
+
+ `Story Table row(s) matched a field name, but didn't match a trigger `
|
|
1420
|
+
+ `variable. The Story Table might be missng a row or a trigger variable. `
|
|
1421
|
+
+ `The test will have to ignore this field. See `
|
|
1422
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1423
|
+
+ `\nThe row(s) data:\n${ conflicts_as_story_table_rows.join( `\n` )}`
|
|
1297
1424
|
+ `\nThis field's data:\n${ JSON.stringify( field )}`;
|
|
1298
1425
|
reports.addToReport(scope, {
|
|
1299
1426
|
type: `warning`,
|
|
@@ -1318,9 +1445,12 @@ module.exports = {
|
|
|
1318
1445
|
|
|
1319
1446
|
let multiple_matches_msg = `Do you have a duplicate story table row? `
|
|
1320
1447
|
+ `Multiple story table rows match this field's variable name. `
|
|
1321
|
-
+ `The test will use the last row that matched
|
|
1322
|
-
|
|
1323
|
-
|
|
1448
|
+
+ `The test will use the last row that matched.`
|
|
1449
|
+
|
|
1450
|
+
if ( trigger_might_be_needed ) {
|
|
1451
|
+
multiple_matches_msg += ` See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1452
|
+
}
|
|
1453
|
+
multiple_matches_msg += `\nThe matching rows:\n${ rows_as_story_table.join(`\n`) }`
|
|
1324
1454
|
+ `\nThis field's data:\n${ JSON.stringify( field )}`;
|
|
1325
1455
|
reports.addToReport(scope, { type: `warning`, value: multiple_matches_msg });
|
|
1326
1456
|
}
|
|
@@ -1374,7 +1504,9 @@ module.exports = {
|
|
|
1374
1504
|
let disabled = await handle.evaluate(( elem )=> {
|
|
1375
1505
|
// da's header covers the top of the section containing the fields which means
|
|
1376
1506
|
// sometimes it blocks nodes from clicks. Scroll item to the bottom instead.
|
|
1377
|
-
|
|
1507
|
+
// Note: I'm not sure how we'd get in here if `elem` didn't exist, but
|
|
1508
|
+
// I once got an error here that I couldn't read `.disabled` of `null`.
|
|
1509
|
+
if ( elem && !elem.disabled ) { elem.scrollIntoView(false); }
|
|
1378
1510
|
return elem.disabled;
|
|
1379
1511
|
});
|
|
1380
1512
|
|
|
@@ -1982,24 +2114,21 @@ module.exports = {
|
|
|
1982
2114
|
|
|
1983
2115
|
// If all table rows must be used and any table row was not, error
|
|
1984
2116
|
if ( ensure_all_vars_used ) {
|
|
1985
|
-
let num_unused_rows = 0;
|
|
1986
2117
|
for ( let row of var_data ) {
|
|
1987
2118
|
if ( row.times_used === 0 ) {
|
|
1988
|
-
|
|
2119
|
+
let values_used_status = 'The page is missing a value';
|
|
1989
2120
|
let source_row = row;
|
|
1990
2121
|
// TODO: Make the row object flatter so we don't need to dig in this way
|
|
1991
2122
|
if ( row.source ) { source_row = row.source; }
|
|
1992
2123
|
let original_row = source_row.original;
|
|
1993
|
-
let
|
|
1994
|
-
|
|
2124
|
+
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
2125
|
+
let msg = `In a linear Step all the values you give MUST be filled in, but ALKiln was unable `
|
|
2126
|
+
+ `to find a field on the page "${id}" for the variable "${ original_row.var }" that `
|
|
2127
|
+
+ `could be set to "${ original_row.value }". See this Scenario's HTML file for details.`;
|
|
2128
|
+
reports.addToReport(scope, { type: `error`, value: msg });
|
|
2129
|
+
expect( values_used_status, msg ).to.equal( '' );
|
|
1995
2130
|
}
|
|
1996
2131
|
}
|
|
1997
|
-
if ( num_unused_rows > 0 ) {
|
|
1998
|
-
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
1999
|
-
let msg = `Error on page "${id}". See screenshot and see text above for more details.`;
|
|
2000
|
-
reports.addToReport(scope, { type: `error`, value: msg });
|
|
2001
|
-
expect( num_unused_rows, msg ).to.equal( 0 );
|
|
2002
|
-
}
|
|
2003
2132
|
}
|
|
2004
2133
|
|
|
2005
2134
|
// If didn't already press a continue button, press one now.
|
|
@@ -2191,7 +2320,7 @@ module.exports = {
|
|
|
2191
2320
|
value: 'The attribute `.there_is_another` is invalid in story table tests. Replace it with '
|
|
2192
2321
|
+ '`.target_number` in your `var` and `trigger` columns. Set the `value` to the number of items in '
|
|
2193
2322
|
+ `that list. This test will now set this row's \`value\` to \`False\`. See `
|
|
2194
|
-
+ `https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/
|
|
2323
|
+
+ `https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/alkiln#there_is_another. `
|
|
2195
2324
|
+ `The row data is\n${ JSON.stringify( var_datum.original )}` });
|
|
2196
2325
|
} // ends if it's a `target_number` row
|
|
2197
2326
|
} // ends for each row
|
|
@@ -2221,7 +2350,7 @@ module.exports = {
|
|
|
2221
2350
|
} else if ( value.match( /(true|yes|checked|check|selected|select)/i )) {
|
|
2222
2351
|
if ( from_story_table && !row.artificial ) {
|
|
2223
2352
|
if ( row.var.match( /\.there_is_another$/ ) ) {
|
|
2224
|
-
reports.addToReport( scope, { type: `warning`, value: `The attribute \`.there_is_another\` is invalid in story table tests. Replace it with \`.target_number\` in your \`var\` column. 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/
|
|
2353
|
+
reports.addToReport( scope, { type: `warning`, value: `The attribute \`.there_is_another\` is invalid in story table tests. Replace it with \`.target_number\` in your \`var\` column. 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/alkiln#there_is_another-loop. The row data is\n${ JSON.stringify( row.original )}` });
|
|
2225
2354
|
} else {
|
|
2226
2355
|
// If `target_number` is set to True instead of a number
|
|
2227
2356
|
reports.addToReport( scope, { type: `warning`, value: `${ val_to_print } value is not a valid value for ${ row.var } here. This test will default to \`False\` to avoid problems.` });
|
|
@@ -2583,6 +2712,7 @@ module.exports = {
|
|
|
2583
2712
|
* 1. Four words - a first, middle, last, and suffix (ex: "Tal Tay Taylor III")
|
|
2584
2713
|
*
|
|
2585
2714
|
* The string 'the var'/'the variable' part of the step is optional, as you can see below.
|
|
2715
|
+
* This is a linear step, so no trigger var is needed.
|
|
2586
2716
|
*/
|
|
2587
2717
|
let name_patterns = [
|
|
2588
2718
|
[ `text` ],
|
|
@@ -2628,6 +2758,7 @@ module.exports = {
|
|
|
2628
2758
|
*
|
|
2629
2759
|
* The string 'the var'/'the variable' part of the step is optional,
|
|
2630
2760
|
* as you can see below.
|
|
2761
|
+
* This is a linear step only, so it doesn't need to have a trigger var.
|
|
2631
2762
|
*/
|
|
2632
2763
|
|
|
2633
2764
|
// Get all the parts of the address properly separated
|