@suffolklitlab/alkiln 5.0.0-pre-7 → 5.0.0-pre-8

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
@@ -59,6 +59,8 @@ Format:
59
59
  - Change interface for testing languages. See [#713](https://github.com/SuffolkLITLab/ALKiln/issues/713).
60
60
 
61
61
  ### Fixed
62
+ - DOCKER UPDATE (for ALKilnInThePlayground): Allows ALKilnInThePlayground to upload files. See https://github.com/SuffolkLITLab/docassemble-ALKilnInThePlayground/issues/18. This may require some developers to update their system (updating their docker image) if they are below [docassemble-os 1.0.8](https://github.com/jhpyle/docassemble-os/releases/tag/v1.0.8). You can tell if you are below docassemble-os 1.0.8 if you run `docker image inspect jhpyle/docassemble | grep Created` on your host server (Lightsail, etc.) and the created date is before 2023-04-09. You can update as described in [the documentation](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/maintaining-docassemble#updates-to-the-docassemble-container). Updating to a new docker images is something that all docassemble users will have to do sometime anyway, sometimes for security.
63
+ - 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.
62
64
  - Projects created in da each have a unique name. https://github.com/SuffolkLITLab/ALKiln/issues/663
63
65
  - 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
64
66
  - Updated field decoding to handle new object field encoding. See [#711](https://github.com/SuffolkLITLab/ALKiln/issues/711)
package/lib/scope.js CHANGED
@@ -2,6 +2,7 @@ const { expect } = require('chai');
2
2
  const cheerio = require('cheerio');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
+ const fg = require('fast-glob');
5
6
  const { AxePuppeteer } = require('@axe-core/puppeteer');
6
7
  const safe_filename = require("sanitize-filename");
7
8
  // "en" is faster to import: https://github.com/faker-js/faker/issues/1114#issuecomment-1169532948
@@ -1528,7 +1529,10 @@ module.exports = {
1528
1529
  let row = matching_input_rows[ matching_input_rows.length - 1 ];
1529
1530
  let answer = row.value;
1530
1531
 
1531
- await scope.funnel_the_answer( scope, { handle, answer, field });
1532
+ // Will implement `was_used` in cases as they come along. For now, this is
1533
+ // necessary for file uploads to be as flexible as any other Story Table row.
1534
+ let was_used = await scope.funnel_the_answer( scope, { handle, answer, field });
1535
+ if ( was_used === false ) { row = null; }
1532
1536
 
1533
1537
  return row;
1534
1538
  }, // Ends scope.setVariable()
@@ -1590,16 +1594,18 @@ module.exports = {
1590
1594
  custom_datatype = await scope.get_custom_datatype( scope, { handle });
1591
1595
  }
1592
1596
 
1597
+ let row_was_used = true;
1593
1598
  // If there is a custom datatype function available, use it to set the field
1594
1599
  if ( scope.setCustomDatatype[ custom_datatype ] !== undefined ) {
1595
1600
  // Set da package custom fields. TODO: find `custom_handle` in the setter?
1596
1601
  let custom_handle = await handle.evaluateHandle(( elem )=> { return elem.closest( `.da-form-group` ) });
1597
- await scope.setCustomDatatype[ custom_datatype ]( scope, { handle: custom_handle, answer });
1602
+ row_was_used = await scope.setCustomDatatype[ custom_datatype ]( scope, { handle: custom_handle, answer });
1598
1603
  } else {
1599
1604
  // If no custom datatype, set a regular field
1600
- await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1605
+ row_was_used = await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1601
1606
  }
1602
1607
 
1608
+ return row_was_used;
1603
1609
  }, // Ends scope.funnel_the_answer()
1604
1610
 
1605
1611
  // Handle setting values for da custom datatypes. E.g. `da-field-container-datatype-BirthDate`
@@ -1660,10 +1666,14 @@ module.exports = {
1660
1666
  }, // ends setCustomDatatype {}
1661
1667
 
1662
1668
  enter_answer: {
1663
- button: async function ( scope, { handle, answer }) { await scope.tapElement( scope, handle ); },
1669
+ button: async function ( scope, { handle, answer }) {
1670
+ await scope.tapElement( scope, handle );
1671
+ return true;
1672
+ },
1664
1673
  textarea: async function ( scope, { handle, answer }) {
1665
1674
  await scope.setText( scope, { handle, answer });
1666
1675
  await scope.afterStep(scope); // No showifs for this one?
1676
+ return true;
1667
1677
  },
1668
1678
  select: async function ( scope, { handle, answer }) {
1669
1679
  // Note: I don't remember what's really going on in here.
@@ -1692,13 +1702,16 @@ module.exports = {
1692
1702
  }
1693
1703
 
1694
1704
  await scope.afterStep(scope, { waitForShowIf: true });
1705
+ return true;
1695
1706
  },
1696
1707
  canvas: async function ( scope, { handle, answer }) {
1697
1708
  await scope.draw_signature( scope );
1698
1709
  await scope.afterStep( scope );
1710
+ return true;
1699
1711
  },
1700
1712
  input: async function ( scope, { handle, answer }) {
1701
1713
  /* Set value of some `input` element to the given value. */
1714
+ let row_was_used = true;
1702
1715
 
1703
1716
  let type = await handle.evaluate( elem => { return elem.getAttribute('type'); });
1704
1717
  if ( type === `radio` || type === `checkbox` ) {
@@ -1717,7 +1730,7 @@ module.exports = {
1717
1730
  await scope.afterStep(scope, { waitForShowIf: true });
1718
1731
 
1719
1732
  } else if ( type === `file` ) {
1720
- await scope.uploadFiles( scope, { handle, answer });
1733
+ row_was_used = await scope.uploadFiles( scope, { handle, answer });
1721
1734
  await scope.afterStep(scope, { waitForShowIf: true });
1722
1735
 
1723
1736
  } else if (type == `date`) {
@@ -1746,6 +1759,8 @@ module.exports = {
1746
1759
  await scope.setText( scope, { handle, answer });
1747
1760
  await scope.afterStep(scope, { waitForShowIf: true });
1748
1761
  }
1762
+
1763
+ return row_was_used;
1749
1764
  }, // Ends scope.enter_answer.input()
1750
1765
  hidden: async function ( scope, { handle, answer }) {
1751
1766
  // Do nothing. The only way the code should get here is if
@@ -1762,89 +1777,98 @@ module.exports = {
1762
1777
  uploadFiles: async function ( scope, { handle, answer }) {
1763
1778
  /* Try to upload one or more files to a file-type input field.
1764
1779
  * Does not submit, so no danger of timeout with server reload.
1780
+ *
1781
+ * Returns whether the row was used or not.
1765
1782
  */
1766
1783
 
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;
1784
+ // First make sure the list of files was defined. Don't remove spaces yet -
1785
+ // allow a typo where someone forgot to include a space after a comma.
1786
+ let uploads_names = answer.split(`,`);
1787
+ if ( uploads_names.length === 0 ) {
1788
+ await scope.addToReport( scope, {
1789
+ type: `error`,
1790
+ value: `Your list of files to upload is empty.`
1791
+ });
1792
+ return false; // The row wasn't used
1790
1793
  }
1791
1794
 
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;
1795
+ let dirs = [];
1796
+ if ( session_vars.get_origin() === `playground` ) {
1797
+ let files_playground = fg.sync(
1798
+ [ `/usr/share/docassemble/files/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}` ],
1799
+ { onlyFiles: false, suppressErrors: true }
1800
+ );
1801
+ let files_s3 = fg.sync(
1802
+ [ `/tmp/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}` ],
1803
+ { onlyFiles: false, suppressErrors: true }
1804
+ );
1805
+
1806
+ dirs = files_playground.concat( files_s3 );
1807
+ } else {
1808
+ dirs = fg.sync(
1809
+ [ `**/docassemble/**/data/sources` ],
1810
+ { onlyFiles: false, suppressErrors: true }
1811
+ );
1808
1812
  }
1809
1813
 
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}).` });
1814
+ if ( session_vars.get_debug() ) {
1815
+ console.log( `All discovered dirs to upload from:`, dirs );
1812
1816
  }
1813
1817
 
1814
- // Allow a typo where someone forgot to include a space after a comma
1815
- let file_names = answer.split(`,`);
1818
+ // Make sure sources directory exists and has files in it
1819
+ if ( dirs.length === 0 ) {
1820
+ await scope.addToReport( scope, {
1821
+ type: `warning`,
1822
+ value: `The test files need to be in the "sources" directory, `
1823
+ + `but ALKiln couldn't find the directory or it was empty.`
1824
+ });
1825
+ return false; // The row wasn't used
1826
+ }
1816
1827
 
1817
- // If a file exist, add it to a list of files to be uploaded. If it doesn't, add a warning.
1828
+ // Get the full paths of all the files
1829
+ let dir = null;
1818
1830
  let paths = [];
1819
- for ( let name of file_names ) {
1831
+ let missing_files = []; // For any user warnings
1832
+ for ( let name of uploads_names ) {
1833
+
1834
+ let trimmed_name = name.trim();
1835
+ let matching_paths = [];
1836
+
1837
+ for ( let one_dir of dirs ) {
1838
+ // accumulate all matching paths
1839
+ let new_paths = fg.sync([ `${ one_dir }/${ trimmed_name }` ]);
1840
+ matching_paths.push( ...new_paths );
1841
+ } // ends for each dir
1842
+
1843
+ if ( matching_paths.length === 0 ) {
1844
+ missing_files.push( trimmed_name );
1845
+ } else {
1846
+ paths.push( ...matching_paths );
1847
+ }
1820
1848
 
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 );
1849
+ } // ends for each file
1825
1850
 
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
- }
1851
+ if ( missing_files.length > 0 ) {
1852
+ await scope.addToReport( scope, {
1853
+ type: `warning`,
1854
+ value: `ALKiln could not find "${ missing_files.join('", "') }" in any of these folders: "${ dirs.join('", "') }".`
1855
+ });
1856
+ }
1857
+
1858
+ // If all files missing, return that the row wasn't used
1859
+ if ( missing_files.length === uploads_names.length ) {
1860
+ return false; // The row wasn't used
1833
1861
  }
1834
1862
 
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
1863
  // Prepare to wait for upload to complete
1840
1864
  let name = await handle.evaluate( elem => { return elem.getAttribute(`name`); });
1841
1865
  let selector = `*[name="${ name }"]`
1842
1866
  let disabled_selector = `${ selector }[disabled]`;
1843
1867
  let enabled_selector = `${ selector }:not([disabled])`;
1844
1868
 
1845
- // This comes before the upload starts
1846
- // Notice `await` is absent. Don't actually wait here.
1847
- // Just start looking for it now. That way we'll catch it before it's gone
1869
+ // This comes before the upload starts.
1870
+ // Notice `await` is absent. Don't actually wait here yet.
1871
+ // Start looking for it before it exists or we might miss it.
1848
1872
  let wait_for_disable = scope.page.waitForSelector( disabled_selector );
1849
1873
 
1850
1874
  // Upload
@@ -1855,8 +1879,9 @@ module.exports = {
1855
1879
  await wait_for_disable;
1856
1880
  // When it's again enabled, the files have finished loading. Or so I have observed.
1857
1881
  await scope.page.waitForSelector( enabled_selector );
1858
-
1859
1882
 
1883
+ // Return that the row was used
1884
+ return true;
1860
1885
  }, // Ends scope.uploadFiles()
1861
1886
 
1862
1887
  setCheckbox: async function ( scope, { label, answer }) {
@@ -2,14 +2,14 @@
2
2
 
3
3
  let _path = require('path');
4
4
  let fs = require('fs');
5
- let glob = require('glob');
5
+ const fg = require('fast-glob');
6
6
 
7
7
  let session_vars = require('./session_vars');
8
8
 
9
9
  // Get the glob filepaths to find all the test files
10
10
  let dir_root = process.env.INIT_CWD; // Path to root of project
11
11
  let test_glob = process.argv[2]; // String of glob (regex for filepaths)
12
- let filepaths = glob.sync( _path.resolve( dir_root, test_glob ) );
12
+ let filepaths = fg.sync( _path.resolve( dir_root, test_glob ) );
13
13
  let flag = '_lang_';
14
14
 
15
15
  let langs = session_vars.get_languages();