@suffolklitlab/alkiln 5.8.0 → 5.8.1-feat2columns1
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 +2 -2
- package/CHANGELOG.md +12 -0
- package/action.yml +29 -14
- package/action_for_github_server/action.yml +37 -21
- package/lib/scope.js +289 -114
- package/lib/steps.js +126 -0
- package/lib/utils/log.js +16 -5
- package/package.json +1 -1
- package/tests/unit_tests/fields.fixtures.js +368 -86
- package/tests/unit_tests/getAllFields.test.js +13 -10
package/lib/scope.js
CHANGED
|
@@ -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
|
}
|
|
@@ -724,13 +721,58 @@ module.exports = {
|
|
|
724
721
|
return result;
|
|
725
722
|
}, // Ends scope.getLoadData()
|
|
726
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
|
+
|
|
727
769
|
normalizeTable: async function ( scope, { var_data=null, raw_var_data=null }) {
|
|
728
770
|
/* Return data derived from cucumber variable-setting data, from either an object format
|
|
729
771
|
* (var_data) or an array format (raw_var_data). `var_data` only is used when calling
|
|
730
772
|
* normalizeTable internally.
|
|
731
773
|
*/
|
|
732
774
|
|
|
733
|
-
if (!var_data && !
|
|
775
|
+
if (!var_data && !raw_var_data) {
|
|
734
776
|
let no_data_msg = `ALKiln Internal error: normalizeTable called w/o var_data or raw_var_data:` +
|
|
735
777
|
` open an issue at https://github.com/SuffolkLITLab/ALKiln/issues/new if you see this!`;
|
|
736
778
|
reports.addToReport( scope, { type: `error`, value: no_data_msg });
|
|
@@ -739,39 +781,52 @@ module.exports = {
|
|
|
739
781
|
|
|
740
782
|
// Always use the raw var data if present; it has the full first row
|
|
741
783
|
if (raw_var_data) {
|
|
784
|
+
|
|
742
785
|
var_data = raw_var_data.hashes();
|
|
743
|
-
|
|
786
|
+
// If this table doesn't have headers
|
|
787
|
+
if (!(`var` in var_data[0]) || !(`value` in var_data[0])) {
|
|
744
788
|
let raw_array = raw_var_data.raw();
|
|
745
789
|
var_data = [];
|
|
746
790
|
for (let row_raw of raw_array) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
791
|
+
|
|
792
|
+
// Wrong number of columns error
|
|
793
|
+
if (row_raw.length < 2 || row_raw.length > 3) {
|
|
794
|
+
let more_or_less = `more`;
|
|
795
|
+
if ( row_raw.length > 3 ) { more_or_less = `less`; }
|
|
796
|
+
let bad_table_msg = `Your Story Table should have ${ more_or_less } than`
|
|
797
|
+
+ ` ${ row_raw.length } columns. Take another look at Story Table documentation at`
|
|
798
|
+
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#story-tables.`
|
|
751
799
|
reports.addToReport( scope, { type: `error`, value: bad_table_msg})
|
|
752
800
|
throw Error(bad_table_msg);
|
|
753
|
-
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (row_raw.length == 2) {
|
|
754
804
|
var_data.push({var: row_raw[0], value: row_raw[1]});
|
|
755
805
|
} else {
|
|
756
806
|
var_data.push({var: row_raw[0], value: row_raw[1], trigger: row_raw[2]});
|
|
757
807
|
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
808
|
+
} // ends for every row in table
|
|
809
|
+
} // ends if this table doesn't have headers
|
|
810
|
+
|
|
811
|
+
} // ends if we have access to the full raw data
|
|
761
812
|
|
|
762
813
|
// Support tables with no 'trigger' column
|
|
763
814
|
let supported_table = [];
|
|
815
|
+
|
|
764
816
|
for ( let row of var_data ) {
|
|
765
817
|
// See tests > unit_tests > tables.fixtures.js > tables.old_to_current_formatting
|
|
766
818
|
let actual_var_value = row.value;
|
|
767
819
|
if (is_secret_var(row)) {
|
|
768
|
-
actual_var_value = process.env[row.flags.secret_name];
|
|
820
|
+
actual_var_value = process.env[ row.flags.secret_name ];
|
|
769
821
|
if ( actual_var_value === undefined ) {
|
|
770
|
-
let not_in_env_msg = `
|
|
771
|
-
|
|
772
|
-
|
|
822
|
+
let not_in_env_msg = `ALKiln could not find the GitHub secret "${ row.flags.secret_name }".`
|
|
823
|
+
+ ` Check for a typo and check your workflow file.`
|
|
824
|
+
+ ` Read more about using GitHub secrets in your workflow file at`
|
|
825
|
+
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#github-secrets.`
|
|
826
|
+
reports.addToReport( scope, { type: `error`, value: not_in_env_msg });
|
|
827
|
+
throw ReferenceError(not_in_env_msg);
|
|
773
828
|
}
|
|
774
|
-
}
|
|
829
|
+
} // ends if is secret var
|
|
775
830
|
|
|
776
831
|
let result = {
|
|
777
832
|
// NOTE: mutating frozen objects will fail silently, so don't mutate this attribute
|
|
@@ -780,9 +835,9 @@ module.exports = {
|
|
|
780
835
|
// Give fake data if needed to prevent some kind of hidden input field from being found
|
|
781
836
|
// See bug at https://github.com/plocket/docassemble-cucumber/issues/79
|
|
782
837
|
// May be able to remove this once converted to `getAllFields`
|
|
783
|
-
var: row.var ||
|
|
838
|
+
var: row.var || `alkiln_no_var_name`,
|
|
784
839
|
value: actual_var_value,
|
|
785
|
-
trigger: row.trigger ||
|
|
840
|
+
trigger: row.trigger || ``,
|
|
786
841
|
// This will track how many times total the row or its connected
|
|
787
842
|
// artificial rows are used. Artificial rows are only added for some rows.
|
|
788
843
|
times_used: 0,
|
|
@@ -791,11 +846,12 @@ module.exports = {
|
|
|
791
846
|
result.used_for[ result.var ] = 0;
|
|
792
847
|
|
|
793
848
|
supported_table.push( result );
|
|
794
|
-
}
|
|
849
|
+
} // ends for every row
|
|
795
850
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
851
|
+
log.debug({
|
|
852
|
+
type: `info`,
|
|
853
|
+
pre: `scope.normalizedTable():\n ${JSON.stringify( supported_table )}`
|
|
854
|
+
})
|
|
799
855
|
|
|
800
856
|
return supported_table;
|
|
801
857
|
}, // Ends scope.normalizeTable()
|
|
@@ -815,17 +871,22 @@ module.exports = {
|
|
|
815
871
|
// This is the new trigger var element that developers will have to add to their interviews.
|
|
816
872
|
// AssemblyLine will add it automatically unless someone overrides the values in their own interview.
|
|
817
873
|
// 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
|
-
}
|
|
874
|
+
let $trigger_elem = $( `#trigger` );
|
|
875
|
+
if ( !$trigger_elem.length ) { $trigger_elem = $( `#sought_variable` ); } // Backwards compatibility
|
|
876
|
+
let encoded_trigger_var = $( $trigger_elem ).data( `variable` ) || ``;
|
|
826
877
|
let trigger = fromBase64( encoded_trigger_var );
|
|
827
878
|
|
|
828
|
-
|
|
879
|
+
let $proxies_node = $( `#alkiln_proxy_var_values` );
|
|
880
|
+
let proxies = {};
|
|
881
|
+
// If possible, get values of `x` and `i` that the dev put on the page
|
|
882
|
+
if ( $proxies_node.length ) {
|
|
883
|
+
// Empty strings are fine since that means that proxy is absent and won't come into play.
|
|
884
|
+
proxies.x = fromBase64( $proxies_node.data( `generic_object` ) );
|
|
885
|
+
for ( let index_var of `ijklmn`.split(``) ) {
|
|
886
|
+
proxies[index_var] = fromBase64( $proxies_node.data( `index_var_${ index_var }` ) );
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
829
890
|
// Some `show if` fields have `names` like '_field_n' instead of their var name. Ex: _field_1
|
|
830
891
|
let field_like_names = await scope.getFieldNamesDict( scope, { $: $ });
|
|
831
892
|
|
|
@@ -845,62 +906,69 @@ module.exports = {
|
|
|
845
906
|
};
|
|
846
907
|
if ( node.attribs.type ) { field_data.type = node.attribs.type; }
|
|
847
908
|
|
|
909
|
+
// Often used for the variable name
|
|
910
|
+
let not_decoded_name = $node.attr( `name` );
|
|
911
|
+
|
|
912
|
+
// `<input type="hidden" name="_save_as" value="c2lnbmF0dXJlXzE">`
|
|
848
913
|
// canvas elements are the only field on a signature page
|
|
849
914
|
if ( $(`#dasigpage`).length > 0 && $(`#dasigpage`)[0].name ) {
|
|
850
915
|
field_data.selector = `#daquestion canvas`;
|
|
851
916
|
field_data.tag = `canvas`;
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
917
|
+
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;
|
|
858
925
|
}
|
|
859
926
|
|
|
860
927
|
// The selector we'll use to set the DOM node's value later
|
|
861
|
-
field_data.selector
|
|
928
|
+
if ( !field_data.selector ) {
|
|
929
|
+
field_data.selector = await scope.getFieldSelector( scope, { $, node });
|
|
930
|
+
}
|
|
862
931
|
|
|
863
|
-
// Often used in the variable name
|
|
864
|
-
let encoded_name = $node.attr( `name` );
|
|
865
932
|
// Action buttons keep their var name in a unique place
|
|
933
|
+
// TODO: add examples of the hrefs
|
|
866
934
|
if ( $( node ).hasClass( 'daquestionactionbutton' )) {
|
|
867
935
|
let href = node.attribs.href;
|
|
868
936
|
let match = href.match( /action=([^&]*)/ );
|
|
869
937
|
if ( match ) {
|
|
870
|
-
|
|
938
|
+
not_decoded_name = match[1];
|
|
871
939
|
} else {
|
|
872
940
|
// 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
|
-
|
|
941
|
+
// This means the first guess of the name should be the not_decoded_name
|
|
942
|
+
// TODO: This only replaces the chars at the start of the string. Is that
|
|
943
|
+
// intentional? Does this `.replace()` need the `g` flag?
|
|
944
|
+
not_decoded_name = href.replace(/^[A-Za-z0-9]*/, '_');
|
|
876
945
|
}
|
|
877
946
|
}
|
|
878
947
|
|
|
879
|
-
if ( !
|
|
948
|
+
if ( !not_decoded_name ) {
|
|
880
949
|
// Some fields, like comboboxes, have a weird var name setup
|
|
881
|
-
|
|
950
|
+
not_decoded_name = $node.attr( `id` );
|
|
882
951
|
}
|
|
883
952
|
|
|
884
953
|
// ====================
|
|
885
954
|
// Table rows
|
|
886
955
|
// ====================
|
|
887
|
-
//
|
|
888
|
-
let
|
|
889
|
-
encoded: encoded_name,
|
|
890
|
-
field_like_names,
|
|
891
|
-
nota: false, // none of the above (detected and overwritten later)
|
|
892
|
-
});
|
|
893
|
-
|
|
956
|
+
// "None of the above" checkbox/object radio options don't contain their variable's name
|
|
957
|
+
let is_none_of_the_above = false;
|
|
894
958
|
if ( $node.hasClass('danota-checkbox') || $node.parents(`.da-field-container-datatype-object_radio`).name ) {
|
|
895
|
-
|
|
959
|
+
is_none_of_the_above = true;
|
|
896
960
|
let first_sib = $(node.parent).find( 'input' )[0];
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
field_like_names,
|
|
900
|
-
nota: true, // none of the above
|
|
901
|
-
});
|
|
961
|
+
not_decoded_name = first_sib.attribs.name;
|
|
962
|
+
// TODO: Do we need daaota as well? ("all of the above")
|
|
902
963
|
}
|
|
903
964
|
|
|
965
|
+
let var_names = await scope.getPossibleDecodedNames( scope, {
|
|
966
|
+
not_decoded: not_decoded_name,
|
|
967
|
+
field_like_names,
|
|
968
|
+
is_none_of_the_above: is_none_of_the_above,
|
|
969
|
+
proxies,
|
|
970
|
+
});
|
|
971
|
+
|
|
904
972
|
// button, radio, checkbox yesno, and maybe others need the value of the `value` attribute
|
|
905
973
|
let values = [];
|
|
906
974
|
if ( node.name === `select` ) {
|
|
@@ -926,8 +994,6 @@ module.exports = {
|
|
|
926
994
|
return await scope.page.evaluate(async function ( elem ) {
|
|
927
995
|
return await get_interview_variables();
|
|
928
996
|
});
|
|
929
|
-
|
|
930
|
-
// return data;
|
|
931
997
|
}, // Ends scope.getPageJSON()
|
|
932
998
|
|
|
933
999
|
getFieldSelector: async function ( scope, { $, node }) {
|
|
@@ -978,10 +1044,11 @@ module.exports = {
|
|
|
978
1044
|
// Ignore blanks, and values that certainly aren't base64 encoded like True and False
|
|
979
1045
|
// Modified from https://stackoverflow.com/a/8571649/11416267
|
|
980
1046
|
// DA doesn't use `==` to pad base 64
|
|
1047
|
+
// TODO: How are 'None', 'True', and 'False' being handled?
|
|
981
1048
|
if (value !== 'None' && value !== 'True' && value !== 'False'
|
|
982
1049
|
&& value.match(da_base64_regex)) {
|
|
983
1050
|
let value_decoded = fromBase64(value);
|
|
984
|
-
let values_decoded = scope.decodeFully( scope, {expr_str: value_decoded });
|
|
1051
|
+
let values_decoded = scope.decodeFully( scope, { expr_str: value_decoded });
|
|
985
1052
|
for (let possible_val of values_decoded) {
|
|
986
1053
|
possible_table_rows.push({var: var_name, value: possible_val, });
|
|
987
1054
|
}
|
|
@@ -1008,12 +1075,20 @@ module.exports = {
|
|
|
1008
1075
|
*/
|
|
1009
1076
|
if ( !$('input[name="_varnames"]')[0] ) { return {}; }
|
|
1010
1077
|
|
|
1078
|
+
// TODO: Why are we docoding the field names here? We could decode them later, right?
|
|
1079
|
+
// In `getPossibleDecodedNames` we could do:
|
|
1080
|
+
// if ( field_like_original_names[ not_decoded ] ) {
|
|
1081
|
+
// still_encoded = field_like_names[ not_decoded ];
|
|
1082
|
+
// decode( still_encoded )
|
|
1083
|
+
// }
|
|
1084
|
+
|
|
1011
1085
|
let field_like_names = {};
|
|
1012
1086
|
let field_like_names_encoded = $('input[name="_varnames"]')[0].attribs.value;
|
|
1013
1087
|
let field_like_names_str = fromBase64( field_like_names_encoded );
|
|
1014
1088
|
let field_like_names_json = JSON.parse( field_like_names_str );
|
|
1015
1089
|
for ( let field_like_key in field_like_names_json ) {
|
|
1016
1090
|
let field_like_DOM_name = fromBase64( field_like_key );
|
|
1091
|
+
// Even with `multiselect`, field names are only encoded once
|
|
1017
1092
|
let field_like_var_name = fromBase64( field_like_names_json[ field_like_key ] );
|
|
1018
1093
|
field_like_names[ field_like_DOM_name ] = field_like_var_name;
|
|
1019
1094
|
}
|
|
@@ -1021,8 +1096,13 @@ module.exports = {
|
|
|
1021
1096
|
return field_like_names;
|
|
1022
1097
|
}, // Ends scope.getFieldNamesDict()
|
|
1023
1098
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1099
|
+
getPossibleDecodedNames: async function ( scope, {
|
|
1100
|
+
not_decoded,
|
|
1101
|
+
field_like_names={},
|
|
1102
|
+
is_none_of_the_above=false,
|
|
1103
|
+
proxies={}
|
|
1104
|
+
}) {
|
|
1105
|
+
/** Given a potentially base64 encoded string and a map of `_field_n` to var names, return
|
|
1026
1106
|
* the decoded possible var names and, sometimes, values that the
|
|
1027
1107
|
* encoded string could be. Only some node names actually use `_field_n`.
|
|
1028
1108
|
*
|
|
@@ -1040,41 +1120,69 @@ module.exports = {
|
|
|
1040
1120
|
* TODO: We'll probably eventually have to search for `[B'encoded']` and such
|
|
1041
1121
|
* iteratively to get all of them. Hasn't cropped up yet, though.
|
|
1042
1122
|
*
|
|
1043
|
-
* @param
|
|
1123
|
+
* @param not_decoded {str} - potentially base64 encoded string from the node
|
|
1044
1124
|
* @param field_like_names {obj} - e.g. `{ _field_0: 'some_checkbox_var_name' }`
|
|
1045
|
-
* @param
|
|
1125
|
+
* @param is_none_of_the_above {bool} - is the field a 'none of the above' field?
|
|
1126
|
+
* @param proxies {obj} - e.g. `{ x: str, i: str, j: str, ... n: str }`. Empty strings
|
|
1127
|
+
* are fine since that means that proxy is absent and won't come into play.
|
|
1128
|
+
*/
|
|
1129
|
+
|
|
1046
1130
|
// E.g. continue buttons that don't set a variable
|
|
1047
|
-
if (
|
|
1131
|
+
if ( not_decoded === undefined ) { return []; }
|
|
1132
|
+
// Not sure it's necessary to have both with and without proxies here
|
|
1133
|
+
let guesses = scope.with_and_without_proxies( scope, {
|
|
1134
|
+
var_name: `${ not_decoded }`,
|
|
1135
|
+
proxies,
|
|
1136
|
+
});
|
|
1048
1137
|
|
|
1049
1138
|
// 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
|
-
|
|
1139
|
+
let decoded_once = fromBase64( not_decoded );
|
|
1140
|
+
// If this is of the format `_field_n`, we need to get the actual matching var name
|
|
1141
|
+
if ( field_like_names[ decoded_once ] ) {
|
|
1142
|
+
decoded_once = field_like_names[ decoded_once ];
|
|
1054
1143
|
}
|
|
1055
1144
|
|
|
1056
1145
|
try {
|
|
1057
|
-
|
|
1058
|
-
|
|
1146
|
+
// We think this never has proxy vars
|
|
1147
|
+
let action_button_json = JSON.parse( decoded_once );
|
|
1148
|
+
if ( action_button_json.action ) { decoded_once = action_button_json.action }
|
|
1059
1149
|
} catch ( error ) {
|
|
1060
1150
|
// Things that are not action buttons can't be parsed this way.
|
|
1061
1151
|
// A special error for action buttons requires passing the node in here too, which is a lot to pass in.
|
|
1062
1152
|
// TODO: Should we move this back into `getAllFields()`?
|
|
1063
1153
|
}
|
|
1064
1154
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1155
|
+
// Include `not_decoded` since the name might not be encoded
|
|
1156
|
+
// TODO: Should we try to replace proxies in `not_decoded`. I can't
|
|
1157
|
+
// think of an occasion where that would be necessary.
|
|
1158
|
+
guesses = guesses.concat( scope.decodeFully( scope, {
|
|
1159
|
+
expr_str: decoded_once,
|
|
1160
|
+
field_like_names,
|
|
1161
|
+
is_none_of_the_above,
|
|
1162
|
+
proxies
|
|
1163
|
+
})
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
return guesses;
|
|
1167
|
+
}, // Ends scope.getPossibleDecodedNames()
|
|
1168
|
+
|
|
1169
|
+
decodeFully: function ( scope, {
|
|
1170
|
+
expr_str,
|
|
1171
|
+
field_like_names = {},
|
|
1172
|
+
is_none_of_the_above=false,
|
|
1173
|
+
proxies={}
|
|
1174
|
+
}) {
|
|
1175
|
+
/**
|
|
1070
1176
|
* Given a DA variable or value name, make sure it's fully decoded from base64.
|
|
1071
|
-
* @param
|
|
1177
|
+
* @param expr_str {str} - a python variable string, potentially partially base64 encoded
|
|
1072
1178
|
* Some examples:
|
|
1073
1179
|
* - my_variable
|
|
1074
1180
|
* - checkboxes_choices[B'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
|
|
1075
1181
|
* - foo[bar][B'Y2hlY2tib3hfb3RoZXJfb3B0XzI']
|
|
1076
1182
|
* @param field_like_names {obj} - e.g. `{ _field_0: 'some_checkbox_var_name' }`
|
|
1077
|
-
* @param
|
|
1183
|
+
* @param is_none_of_the_above {bool} - is the field a 'none of the above' field?
|
|
1184
|
+
* @param proxies {obj} - e.g. `{ x: str, i: str, j: str, ... n: str }`. Empty strings
|
|
1185
|
+
* are fine since that means that proxy is absent and won't come into play.
|
|
1078
1186
|
*/
|
|
1079
1187
|
// Some fields have a key hidden inside them.
|
|
1080
1188
|
// This gets the variable name before the final brackets, and the likely
|
|
@@ -1094,22 +1202,37 @@ module.exports = {
|
|
|
1094
1202
|
if ( before_and_in_brackets ) {
|
|
1095
1203
|
let key_or_name = before_and_in_brackets[1];
|
|
1096
1204
|
// If this is of the format `_field_n`, we need to get the actual matching var name.
|
|
1097
|
-
let var_name = field_like_names
|
|
1205
|
+
let var_name = field_like_names[ key_or_name ] || key_or_name;
|
|
1098
1206
|
|
|
1099
1207
|
let dictionary_key = before_and_in_brackets[2];
|
|
1100
1208
|
|
|
1101
1209
|
// Our own marker for a 'none of the above' checkbox choice
|
|
1102
|
-
if (
|
|
1103
|
-
possible.push( `${ var_name }['None']` );
|
|
1210
|
+
if ( is_none_of_the_above ) {
|
|
1211
|
+
// possible.push( `${ var_name }['None']` );
|
|
1212
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1213
|
+
var_name: `${ var_name }['None']`,
|
|
1214
|
+
proxies,
|
|
1215
|
+
}));
|
|
1104
1216
|
} else {
|
|
1217
|
+
|
|
1218
|
+
// TODO: Just use try/catches?
|
|
1219
|
+
|
|
1105
1220
|
if (dictionary_key.match(da_base64_regex)) {
|
|
1106
1221
|
// Get the choice name. E.g. option_1, option_2
|
|
1107
1222
|
let dictionary_key_decoded = fromBase64( dictionary_key );
|
|
1108
|
-
possible.push( `${ var_name }['${ dictionary_key_decoded }']` );
|
|
1223
|
+
// possible.push( `${ var_name }['${ dictionary_key_decoded }']` );
|
|
1224
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1225
|
+
var_name: `${ var_name }['${ dictionary_key_decoded }']`,
|
|
1226
|
+
proxies,
|
|
1227
|
+
}));
|
|
1109
1228
|
if (dictionary_key_decoded.match(da_base64_regex)) {
|
|
1110
1229
|
try {
|
|
1111
|
-
// The part inside `[B'encoded']` could need one more decoding.
|
|
1112
|
-
possible.push(`${ var_name }['${ fromBase64( dictionary_key_decoded ) }']`);
|
|
1230
|
+
// The part inside `[B|R|O'encoded']` could need one more decoding.
|
|
1231
|
+
// possible.push(`${ var_name }['${ fromBase64( dictionary_key_decoded ) }']`);
|
|
1232
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1233
|
+
var_name: `${ var_name }['${ fromBase64( dictionary_key_decoded ) }']`,
|
|
1234
|
+
proxies,
|
|
1235
|
+
}));
|
|
1113
1236
|
} catch ( err ) {} // It wasn't double encoded. Do nothing.
|
|
1114
1237
|
}
|
|
1115
1238
|
}
|
|
@@ -1118,18 +1241,65 @@ module.exports = {
|
|
|
1118
1241
|
// =====================
|
|
1119
1242
|
// If bracket regex didn't match, just use var name
|
|
1120
1243
|
} else {
|
|
1121
|
-
possible.
|
|
1244
|
+
possible = possible.concat( scope.with_and_without_proxies( scope, {
|
|
1245
|
+
var_name: `${ expr_str }`,
|
|
1246
|
+
proxies,
|
|
1247
|
+
}) );
|
|
1122
1248
|
}
|
|
1123
1249
|
return possible;
|
|
1124
1250
|
},
|
|
1125
1251
|
|
|
1252
|
+
with_and_without_proxies: function ( scope, { var_name, proxies={} }) {
|
|
1253
|
+
/** Given a variable or value's possible name and a map of values for
|
|
1254
|
+
* each proxy variable, return a list of either one or two strings.
|
|
1255
|
+
* It always includes the given variable name. If proxies were found
|
|
1256
|
+
* and replaced, the list also includes a version of the variable
|
|
1257
|
+
* name in which the proxies have been replaced by their values.
|
|
1258
|
+
*
|
|
1259
|
+
* Proxies are generic objects (x) or index variables (i, j, k, l, m, n).
|
|
1260
|
+
*
|
|
1261
|
+
* @param { string } var_name - The possible name of a variable or value
|
|
1262
|
+
* @param { Object } proxies - A map of proxy variables to their values on
|
|
1263
|
+
* the HTML page, if they're there.
|
|
1264
|
+
*
|
|
1265
|
+
* @returns {Array<string>} - An array of at least one string - var_name
|
|
1266
|
+
* itself. Possibly a second value where proxy variables in var_name
|
|
1267
|
+
* are replaced with their values (if the proxy variables are present
|
|
1268
|
+
* and if their values are passed in).
|
|
1269
|
+
* */
|
|
1270
|
+
// We could combine these regexes, but this may be easier to read
|
|
1271
|
+
let generic_obj_regex = /^(x)\b/;
|
|
1272
|
+
let index_var_regex = /(?<=\[)([ijklmn])(?=\])/g;
|
|
1273
|
+
// If proxies exist in the HTML, replace any proxies in the variables
|
|
1274
|
+
// with those values.
|
|
1275
|
+
let new_name = var_name;
|
|
1276
|
+
if ( proxies.x !== '' && proxies.x !== undefined ) {
|
|
1277
|
+
new_name = new_name.replace( generic_obj_regex, proxies.x );
|
|
1278
|
+
}
|
|
1279
|
+
new_name = new_name.replace( index_var_regex, function(match) {
|
|
1280
|
+
if ( proxies[ match ] === '' || proxies[ match ] === undefined ) {
|
|
1281
|
+
return match;
|
|
1282
|
+
} else {
|
|
1283
|
+
return proxies[ match ];
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
// The older versions of Story Table need to see "x", "i", etc.,
|
|
1288
|
+
// so keep that old name around.
|
|
1289
|
+
let unique_names = [ var_name ];
|
|
1290
|
+
// If there were proxies, add the new version of the name
|
|
1291
|
+
if ( new_name !== var_name ) { unique_names.push( new_name ); }
|
|
1292
|
+
|
|
1293
|
+
return unique_names;
|
|
1294
|
+
}, // Ends scope.with_and_without_proxies()
|
|
1295
|
+
|
|
1126
1296
|
getOptionValues: async function ( scope, { $select }) {
|
|
1127
1297
|
/* Returns all the option node values of the select node. */
|
|
1128
1298
|
let options = [];
|
|
1129
1299
|
let option_nodes = $select.find( `option` );
|
|
1130
1300
|
for ( let opt of option_nodes ) {
|
|
1131
1301
|
options.push( opt.attribs.value );
|
|
1132
|
-
// Dropdowns created w/ objects are base64 encoded; but it's handled in
|
|
1302
|
+
// Dropdowns created w/ objects are base64 encoded; but it's handled in getPossibleTableRowMatchers
|
|
1133
1303
|
}
|
|
1134
1304
|
return options;
|
|
1135
1305
|
}, // Ends scope.getOptionValues()
|
|
@@ -1137,11 +1307,11 @@ module.exports = {
|
|
|
1137
1307
|
|
|
1138
1308
|
// TODO: Maybe add `from_story_table` to the var data table (array...)
|
|
1139
1309
|
getMatchingRows: async function ( scope, { field, var_data }) {
|
|
1140
|
-
|
|
1310
|
+
/** Given var_data, a list of objects with variable data for
|
|
1141
1311
|
* setting fields, and an object for a DOM field, returns list of the
|
|
1142
1312
|
* rows that match the DOM field. That list of story table rows may be empty.
|
|
1143
1313
|
*
|
|
1144
|
-
* For notes on trigger var, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
1314
|
+
* For notes on trigger var, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger
|
|
1145
1315
|
*
|
|
1146
1316
|
* @param var_data {arr} - fields and the values they can be set to
|
|
1147
1317
|
* @param var_data[n].var {str} - name of variable to set
|
|
@@ -1205,6 +1375,7 @@ module.exports = {
|
|
|
1205
1375
|
// If the field uses proxy variables - x, i, j, etc, flag it. See "proxies" note.
|
|
1206
1376
|
uses_proxies = uses_proxies || guess.var.match( proxies_regex );
|
|
1207
1377
|
|
|
1378
|
+
// TODO: can we just use `uses_proxies` in here?
|
|
1208
1379
|
if ( table_row.trigger === scope.trigger_not_needed_flag ) {
|
|
1209
1380
|
trigger_might_be_needed = false;
|
|
1210
1381
|
}
|
|
@@ -1235,9 +1406,9 @@ module.exports = {
|
|
|
1235
1406
|
// field/page trigger name is needed but is missing.
|
|
1236
1407
|
if ( !page_trigger_name ) {
|
|
1237
1408
|
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/
|
|
1409
|
+
+ `variable or a generic object, but the page is missing `
|
|
1410
|
+
+ `the ALKiln "trigger" variable's name. See `
|
|
1411
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#a-missing-trigger-variable.`;
|
|
1241
1412
|
reports.addToReport(scope, { type: `warning`, value: page_trigger_warning });
|
|
1242
1413
|
}
|
|
1243
1414
|
|
|
@@ -1247,18 +1418,18 @@ module.exports = {
|
|
|
1247
1418
|
|
|
1248
1419
|
if ( !table_row_trigger_name ) {
|
|
1249
1420
|
// 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.
|
|
1421
|
+
// only one page in the proxy var loop and there's only one row (like the "I set the var"
|
|
1422
|
+
// Step), trigger name is not needed, but I'm not sure how we figure out all those
|
|
1423
|
+
// things here and log a useful message.
|
|
1253
1424
|
|
|
1254
1425
|
// First, make the row data more readable to humans.
|
|
1255
1426
|
let row_as_story_table = reports.convertToOriginalStoryTableRow( scope, { row_data: row_that_matches_this_field });
|
|
1256
|
-
let row_trigger_warning = `This
|
|
1427
|
+
let row_trigger_warning = `This Story Table row needs a value in the "trigger" `
|
|
1257
1428
|
+ `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 )}`;
|
|
1429
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1430
|
+
+ `\nThe rows:\n${ row_as_story_table }`;
|
|
1261
1431
|
reports.addToReport(scope, { type: `warning`, value: row_trigger_warning });
|
|
1432
|
+
|
|
1262
1433
|
rows_without_a_trigger_name.push( row_that_matches_this_field );
|
|
1263
1434
|
} else {
|
|
1264
1435
|
let triggers_match = await scope.matchNeutralizedQuotes( scope, { value1: page_trigger_name, value2: table_row_trigger_name });
|
|
@@ -1278,7 +1449,7 @@ module.exports = {
|
|
|
1278
1449
|
}
|
|
1279
1450
|
|
|
1280
1451
|
// Warn the user if all matching rows had trigger names that conflicted.
|
|
1281
|
-
// Missing trigger names don't count as conflicting.
|
|
1452
|
+
// Missing trigger names don't count as conflicting. [Did this mean missing in the table or in the page?]
|
|
1282
1453
|
if ( final_matching_rows.length === 0 ) {
|
|
1283
1454
|
|
|
1284
1455
|
// First, make the row data more readable to humans.
|
|
@@ -1288,12 +1459,12 @@ module.exports = {
|
|
|
1288
1459
|
conflicts_as_story_table_rows.push( table_str );
|
|
1289
1460
|
}
|
|
1290
1461
|
|
|
1291
|
-
let all_trigger_names_conflict_msg =
|
|
1292
|
-
+
|
|
1293
|
-
+ `
|
|
1294
|
-
+ `
|
|
1295
|
-
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
1296
|
-
+ `\nThe
|
|
1462
|
+
let all_trigger_names_conflict_msg = `${ rows_that_match_field_names.length } `
|
|
1463
|
+
+ `Story Table row(s) matched a field name, but didn't match a trigger `
|
|
1464
|
+
+ `variable. The Story Table might be missng a row or a trigger variable. `
|
|
1465
|
+
+ `The test will have to ignore this field. See `
|
|
1466
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1467
|
+
+ `\nThe row(s) data:\n${ conflicts_as_story_table_rows.join( `\n` )}`
|
|
1297
1468
|
+ `\nThis field's data:\n${ JSON.stringify( field )}`;
|
|
1298
1469
|
reports.addToReport(scope, {
|
|
1299
1470
|
type: `warning`,
|
|
@@ -1319,7 +1490,7 @@ module.exports = {
|
|
|
1319
1490
|
let multiple_matches_msg = `Do you have a duplicate story table row? `
|
|
1320
1491
|
+ `Multiple story table rows match this field's variable name. `
|
|
1321
1492
|
+ `The test will use the last row that matched. See `
|
|
1322
|
-
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
1493
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1323
1494
|
+ `\nThe matching rows:\n${ rows_as_story_table.join(`\n`) }`
|
|
1324
1495
|
+ `\nThis field's data:\n${ JSON.stringify( field )}`;
|
|
1325
1496
|
reports.addToReport(scope, { type: `warning`, value: multiple_matches_msg });
|
|
@@ -1374,7 +1545,9 @@ module.exports = {
|
|
|
1374
1545
|
let disabled = await handle.evaluate(( elem )=> {
|
|
1375
1546
|
// da's header covers the top of the section containing the fields which means
|
|
1376
1547
|
// sometimes it blocks nodes from clicks. Scroll item to the bottom instead.
|
|
1377
|
-
|
|
1548
|
+
// Note: I'm not sure how we'd get in here if `elem` didn't exist, but
|
|
1549
|
+
// I once got an error here that I couldn't read `.disabled` of `null`.
|
|
1550
|
+
if ( elem && !elem.disabled ) { elem.scrollIntoView(false); }
|
|
1378
1551
|
return elem.disabled;
|
|
1379
1552
|
});
|
|
1380
1553
|
|
|
@@ -2191,7 +2364,7 @@ module.exports = {
|
|
|
2191
2364
|
value: 'The attribute `.there_is_another` is invalid in story table tests. Replace it with '
|
|
2192
2365
|
+ '`.target_number` in your `var` and `trigger` columns. Set the `value` to the number of items in '
|
|
2193
2366
|
+ `that list. This test will now set this row's \`value\` to \`False\`. See `
|
|
2194
|
-
+ `https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/
|
|
2367
|
+
+ `https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/alkiln#there_is_another. `
|
|
2195
2368
|
+ `The row data is\n${ JSON.stringify( var_datum.original )}` });
|
|
2196
2369
|
} // ends if it's a `target_number` row
|
|
2197
2370
|
} // ends for each row
|
|
@@ -2221,7 +2394,7 @@ module.exports = {
|
|
|
2221
2394
|
} else if ( value.match( /(true|yes|checked|check|selected|select)/i )) {
|
|
2222
2395
|
if ( from_story_table && !row.artificial ) {
|
|
2223
2396
|
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/
|
|
2397
|
+
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
2398
|
} else {
|
|
2226
2399
|
// If `target_number` is set to True instead of a number
|
|
2227
2400
|
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 +2756,7 @@ module.exports = {
|
|
|
2583
2756
|
* 1. Four words - a first, middle, last, and suffix (ex: "Tal Tay Taylor III")
|
|
2584
2757
|
*
|
|
2585
2758
|
* The string 'the var'/'the variable' part of the step is optional, as you can see below.
|
|
2759
|
+
* This is a linear step, so no trigger var is needed.
|
|
2586
2760
|
*/
|
|
2587
2761
|
let name_patterns = [
|
|
2588
2762
|
[ `text` ],
|
|
@@ -2628,6 +2802,7 @@ module.exports = {
|
|
|
2628
2802
|
*
|
|
2629
2803
|
* The string 'the var'/'the variable' part of the step is optional,
|
|
2630
2804
|
* as you can see below.
|
|
2805
|
+
* This is a linear step only, so it doesn't need to have a trigger var.
|
|
2631
2806
|
*/
|
|
2632
2807
|
|
|
2633
2808
|
// Get all the parts of the address properly separated
|