@suffolklitlab/alkiln 5.0.0-experimental1 → 5.0.0-experimental3

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/CHANGELOG.md CHANGED
@@ -62,6 +62,7 @@ Format:
62
62
  - Shorten path names to try to accommodate limitations of windows systems while still keeping enough useful information to help devs identify the test outputs. https://github.com/SuffolkLITLab/ALKiln/issues/618
63
63
  - Updated field decoding to handle new object field encoding. See [#711](https://github.com/SuffolkLITLab/ALKiln/issues/711)
64
64
  - Allows ALKilnInThePlayground to upload files. See https://github.com/SuffolkLITLab/docassemble-ALKilnInThePlayground/issues/18.
65
+ - Conform upload file behavior to that of other Story Table rows - avoid erroring when field isn't set properly unless using all variables is required.
65
66
 
66
67
  ### Security
67
68
  - Pass docassemble API keys through HTTP headers instead of as parameters.
package/lib/scope.js CHANGED
@@ -2,6 +2,8 @@ const { expect } = require('chai');
2
2
  const cheerio = require('cheerio');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
+ // const { globSync } = require('glob');
6
+ const fg = require('fast-glob');
5
7
  const { AxePuppeteer } = require('@axe-core/puppeteer');
6
8
  const safe_filename = require("sanitize-filename");
7
9
  // "en" is faster to import: https://github.com/faker-js/faker/issues/1114#issuecomment-1169532948
@@ -1528,7 +1530,10 @@ module.exports = {
1528
1530
  let row = matching_input_rows[ matching_input_rows.length - 1 ];
1529
1531
  let answer = row.value;
1530
1532
 
1531
- await scope.funnel_the_answer( scope, { handle, answer, field });
1533
+ // Will implement `was_used` in cases as they come along. For now, this is
1534
+ // necessary for file uploads to be as flexible as any other Story Table row.
1535
+ let was_used = await scope.funnel_the_answer( scope, { handle, answer, field });
1536
+ if ( was_used === false ) { row = null; }
1532
1537
 
1533
1538
  return row;
1534
1539
  }, // Ends scope.setVariable()
@@ -1590,16 +1595,18 @@ module.exports = {
1590
1595
  custom_datatype = await scope.get_custom_datatype( scope, { handle });
1591
1596
  }
1592
1597
 
1598
+ let row_was_used = true;
1593
1599
  // If there is a custom datatype function available, use it to set the field
1594
1600
  if ( scope.setCustomDatatype[ custom_datatype ] !== undefined ) {
1595
1601
  // Set da package custom fields. TODO: find `custom_handle` in the setter?
1596
1602
  let custom_handle = await handle.evaluateHandle(( elem )=> { return elem.closest( `.da-form-group` ) });
1597
- await scope.setCustomDatatype[ custom_datatype ]( scope, { handle: custom_handle, answer });
1603
+ row_was_used = await scope.setCustomDatatype[ custom_datatype ]( scope, { handle: custom_handle, answer });
1598
1604
  } else {
1599
1605
  // If no custom datatype, set a regular field
1600
- await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1606
+ row_was_used = await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1601
1607
  }
1602
1608
 
1609
+ return row_was_used;
1603
1610
  }, // Ends scope.funnel_the_answer()
1604
1611
 
1605
1612
  // Handle setting values for da custom datatypes. E.g. `da-field-container-datatype-BirthDate`
@@ -1660,10 +1667,14 @@ module.exports = {
1660
1667
  }, // ends setCustomDatatype {}
1661
1668
 
1662
1669
  enter_answer: {
1663
- button: async function ( scope, { handle, answer }) { await scope.tapElement( scope, handle ); },
1670
+ button: async function ( scope, { handle, answer }) {
1671
+ await scope.tapElement( scope, handle );
1672
+ return true;
1673
+ },
1664
1674
  textarea: async function ( scope, { handle, answer }) {
1665
1675
  await scope.setText( scope, { handle, answer });
1666
1676
  await scope.afterStep(scope); // No showifs for this one?
1677
+ return true;
1667
1678
  },
1668
1679
  select: async function ( scope, { handle, answer }) {
1669
1680
  // Note: I don't remember what's really going on in here.
@@ -1692,13 +1703,16 @@ module.exports = {
1692
1703
  }
1693
1704
 
1694
1705
  await scope.afterStep(scope, { waitForShowIf: true });
1706
+ return true;
1695
1707
  },
1696
1708
  canvas: async function ( scope, { handle, answer }) {
1697
1709
  await scope.draw_signature( scope );
1698
1710
  await scope.afterStep( scope );
1711
+ return true;
1699
1712
  },
1700
1713
  input: async function ( scope, { handle, answer }) {
1701
1714
  /* Set value of some `input` element to the given value. */
1715
+ let row_was_used = true;
1702
1716
 
1703
1717
  let type = await handle.evaluate( elem => { return elem.getAttribute('type'); });
1704
1718
  if ( type === `radio` || type === `checkbox` ) {
@@ -1717,7 +1731,7 @@ module.exports = {
1717
1731
  await scope.afterStep(scope, { waitForShowIf: true });
1718
1732
 
1719
1733
  } else if ( type === `file` ) {
1720
- await scope.uploadFiles( scope, { handle, answer });
1734
+ row_was_used = await scope.uploadFiles( scope, { handle, answer });
1721
1735
  await scope.afterStep(scope, { waitForShowIf: true });
1722
1736
 
1723
1737
  } else if (type == `date`) {
@@ -1746,6 +1760,8 @@ module.exports = {
1746
1760
  await scope.setText( scope, { handle, answer });
1747
1761
  await scope.afterStep(scope, { waitForShowIf: true });
1748
1762
  }
1763
+
1764
+ return row_was_used;
1749
1765
  }, // Ends scope.enter_answer.input()
1750
1766
  hidden: async function ( scope, { handle, answer }) {
1751
1767
  // Do nothing. The only way the code should get here is if
@@ -1762,80 +1778,57 @@ module.exports = {
1762
1778
  uploadFiles: async function ( scope, { handle, answer }) {
1763
1779
  /* Try to upload one or more files to a file-type input field.
1764
1780
  * Does not submit, so no danger of timeout with server reload.
1781
+ *
1782
+ * Returns whether the row was used or not.
1765
1783
  */
1784
+ console.log( process.cwd() );
1766
1785
 
1767
- // Tests should be one place: docassemble/*/data/sources, where * is the only folder in `docassemble`.
1768
- // Makes an absolute path from the folder in which `npm run` was used plus our internal folders
1769
- let docassemble_pkg_path = `${ process.cwd() }/docassemble`;
1770
- let subpackage_name = session_vars.get_package_name().substring(12);
1771
- let pkg_paths = [];
1772
- try {
1773
- fs.accessSync(docassemble_pkg_path);
1774
- // https://stackoverflow.com/a/24594123/11416267
1775
- let dirs = fs.readdirSync(docassemble_pkg_path, {withFileTypes: true}).filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
1776
- if (dirs.length < 1) {
1777
- let msg = `You can store files to upload in 'docassemble/${ subpackage_name }/data/source'. These tests could not find a folder like that starting at ${ process.cwd() }.`;
1778
- await scope.addToReport( scope, { type: `warning`, value: msg});
1779
- throw Error(msg); // Most people will want to know the sources folder path;
1780
- }
1781
- for (let dir of dirs) {
1782
- pkg_paths.push(`${docassemble_pkg_path}/${dir}`);
1783
- }
1784
- } catch ( error_da ) {
1785
- let msg = `You can store files to upload in 'docassemble/${ subpackage_name }/data/source'. ` +
1786
- `These tests could not find a folder like that. The current directory is ` +
1787
- ` ${ process.cwd() }; is that in the top level folder of your docassemble package? (${ session_vars.get_package_name() })?`;
1788
- await scope.addToReport( scope, { type: `warning`, value: msg});
1789
- throw error_da; // Most people will want to know the sources folder path;
1790
- }
1791
-
1792
- let chosen_dir = null;
1793
- let err_paths = [];
1794
- for (let pkg_path of pkg_paths) {
1795
- let dir = `${ pkg_path }/data/sources`;
1796
- try {
1797
- fs.accessSync(dir);
1798
- chosen_dir = dir;
1799
- break;
1800
- } catch ( error_full ) {
1801
- err_paths.push(`'${dir}'`);
1802
- }
1803
- }
1804
- if (chosen_dir === null) {
1805
- let msg = `You can store files to upload in ${err_paths.join(", or ")}. These tests could not find any of those folders.`;
1806
- await scope.addToReport( scope, { type: `warning`, value: msg });
1807
- throw Error(msg); // Most people will want to know the sources folder path;
1786
+ // First make sure the list of files is of the correct format
1787
+ // Allow a typo where someone forgot to include a space after a comma
1788
+ let uploads_names = answer.split(`,`);
1789
+ if ( uploads_names.length === 0 ) {
1790
+ await scope.addToReport( scope, {
1791
+ type: `error`,
1792
+ value: `Your list of files to upload is empty.`
1793
+ });
1794
+ return false;
1808
1795
  }
1809
1796
 
1810
- if (pkg_paths.length > 1 && pkg_paths.length - err_paths.length > 1) {
1811
- await scope.addToReport( scope, { type: `warning`, value: `You have multiple folders in the 'docassemble' folder (${ pkg_paths }). These tests will use the first folder with a "data/sources" subfolder (${chosen_dir}).` });
1797
+ // Make sure sources directory exists
1798
+ let dirs = fg.sync([ `**/data/sources` ], { onlyFiles: false });
1799
+ if ( dirs.length === 0 ) {
1800
+ await scope.addToReport( scope, {
1801
+ type: `warning`,
1802
+ value: `The test files need to be in the "sources" directory anywhere within "${ process.cwd() }", but ALKiln couldn't find it.`
1803
+ });
1804
+ return false;
1812
1805
  }
1806
+ let dir = dirs[0];
1813
1807
 
1814
- // Allow a typo where someone forgot to include a space after a comma
1815
- let file_names = answer.split(`,`);
1816
-
1817
- // If a file exist, add it to a list of files to be uploaded. If it doesn't, add a warning.
1818
1808
  let paths = [];
1819
- for ( let name of file_names ) {
1809
+ let missing_files = [];
1810
+ for ( let name of uploads_names ) {
1811
+ let trimmed_name = name.trim();
1812
+ let matching_paths = fg.sync([ `${ dir }/${ trimmed_name }` ]);
1813
+ if ( matching_paths.length === 0 ) {
1814
+ missing_files.push( trimmed_name );
1815
+ } else {
1816
+ paths.push( matching_paths[0] );
1817
+ }
1818
+ }
1820
1819
 
1821
- // If the developer correctly used a ", ", there will be a space at the
1822
- // start of the string. Remove it. If they forgot the space, that's fine too.
1823
- name = name.replace(/^\s/, '');
1824
- let file_path = path.join( chosen_dir, name );
1820
+ if ( missing_files.length > 0 ) {
1821
+ await scope.addToReport( scope, {
1822
+ type: `warning`,
1823
+ value: `ALKiln could not find "${ missing_files.join('", "') }" in "${ dir }".`
1824
+ });
1825
+ }
1825
1826
 
1826
- try {
1827
- fs.accessSync( file_path );
1828
- paths.push( file_path );
1829
- } catch ( err ) {
1830
- // TODO: Should this throw an error?
1831
- await scope.addToReport( scope, { type: `warning`, value: `Could not find "${ name }" in "${ chosen_dir }"` });
1832
- }
1827
+ // If all files missing, return that the row wasn't used
1828
+ if ( missing_files.length === uploads_names.length ) {
1829
+ return false;
1833
1830
  }
1834
1831
 
1835
- if ( paths.length === 0 ) {
1836
- await scope.addToReport( scope, { type: `error`, value: `Could not find "${answer}".` });
1837
- throw ReferenceError(`Could not find "${answer}".`)
1838
- }
1839
1832
  // Prepare to wait for upload to complete
1840
1833
  let name = await handle.evaluate( elem => { return elem.getAttribute(`name`); });
1841
1834
  let selector = `*[name="${ name }"]`
@@ -1855,8 +1848,9 @@ module.exports = {
1855
1848
  await wait_for_disable;
1856
1849
  // When it's again enabled, the files have finished loading. Or so I have observed.
1857
1850
  await scope.page.waitForSelector( enabled_selector );
1858
-
1859
1851
 
1852
+ // Return that the row was used
1853
+ return true;
1860
1854
  }, // Ends scope.uploadFiles()
1861
1855
 
1862
1856
  setCheckbox: async function ( scope, { label, answer }) {