jspsych 6.3.0 → 7.1.1

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.
Files changed (397) hide show
  1. package/README.md +36 -37
  2. package/css/jspsych.css +39 -39
  3. package/dist/JsPsych.d.ts +112 -0
  4. package/dist/TimelineNode.d.ts +34 -0
  5. package/dist/index.browser.js +3164 -0
  6. package/dist/index.browser.js.map +1 -0
  7. package/dist/index.browser.min.js +2 -0
  8. package/dist/index.browser.min.js.map +1 -0
  9. package/dist/index.cjs +3158 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.js +3152 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/migration.d.ts +3 -0
  15. package/dist/modules/data/DataCollection.d.ts +45 -0
  16. package/dist/modules/data/DataColumn.d.ts +15 -0
  17. package/dist/modules/data/index.d.ts +25 -0
  18. package/dist/modules/data/utils.d.ts +3 -0
  19. package/dist/modules/extensions.d.ts +22 -0
  20. package/dist/modules/plugin-api/HardwareAPI.d.ts +15 -0
  21. package/dist/modules/plugin-api/KeyboardListenerAPI.d.ts +34 -0
  22. package/dist/modules/plugin-api/MediaAPI.d.ts +27 -0
  23. package/dist/modules/plugin-api/SimulationAPI.d.ts +41 -0
  24. package/dist/modules/plugin-api/TimeoutAPI.d.ts +5 -0
  25. package/dist/modules/plugin-api/index.d.ts +8 -0
  26. package/dist/modules/plugins.d.ts +129 -0
  27. package/dist/modules/randomization.d.ts +35 -0
  28. package/dist/modules/turk.d.ts +40 -0
  29. package/dist/modules/utils.d.ts +7 -0
  30. package/package.json +32 -15
  31. package/src/JsPsych.ts +884 -0
  32. package/src/TimelineNode.ts +536 -0
  33. package/src/index.ts +71 -0
  34. package/src/migration.ts +37 -0
  35. package/src/modules/data/DataCollection.ts +198 -0
  36. package/src/modules/data/DataColumn.ts +86 -0
  37. package/src/modules/data/index.ts +174 -0
  38. package/src/modules/data/utils.ts +75 -0
  39. package/src/modules/extensions.ts +23 -0
  40. package/src/modules/plugin-api/HardwareAPI.ts +32 -0
  41. package/src/modules/plugin-api/KeyboardListenerAPI.ts +165 -0
  42. package/src/modules/plugin-api/MediaAPI.ts +337 -0
  43. package/src/modules/plugin-api/SimulationAPI.ts +181 -0
  44. package/src/modules/plugin-api/TimeoutAPI.ts +16 -0
  45. package/src/modules/plugin-api/index.ts +28 -0
  46. package/src/modules/plugins.ts +158 -0
  47. package/src/modules/randomization.ts +327 -0
  48. package/src/modules/turk.ts +99 -0
  49. package/src/modules/utils.ts +30 -0
  50. package/.github/workflows/jest.yml +0 -20
  51. package/code-of-conduct.md +0 -56
  52. package/contributors.md +0 -61
  53. package/docs/CNAME +0 -1
  54. package/docs/about/about.md +0 -18
  55. package/docs/about/contributing.md +0 -43
  56. package/docs/about/license.md +0 -25
  57. package/docs/about/support.md +0 -7
  58. package/docs/core_library/jspsych-core.md +0 -734
  59. package/docs/core_library/jspsych-data.md +0 -589
  60. package/docs/core_library/jspsych-pluginAPI.md +0 -610
  61. package/docs/core_library/jspsych-randomization.md +0 -397
  62. package/docs/core_library/jspsych-turk.md +0 -102
  63. package/docs/extensions/extensions.md +0 -83
  64. package/docs/extensions/jspsych-ext-webgazer.md +0 -106
  65. package/docs/img/blue.png +0 -0
  66. package/docs/img/devtools-change-css.png +0 -0
  67. package/docs/img/devtools-css-errors.png +0 -0
  68. package/docs/img/devtools-inspect-element.png +0 -0
  69. package/docs/img/folder-setup.png +0 -0
  70. package/docs/img/folder-with-html.png +0 -0
  71. package/docs/img/githubreleases.jpg +0 -0
  72. package/docs/img/jspsych-favicon.png +0 -0
  73. package/docs/img/jspsych-logo-no-text-mono.svg +0 -493
  74. package/docs/img/jspsych-logo.jpg +0 -0
  75. package/docs/img/orange.png +0 -0
  76. package/docs/img/palmer_stim.png +0 -0
  77. package/docs/img/progress_bar.png +0 -0
  78. package/docs/img/prolific-study-completion.png +0 -0
  79. package/docs/img/prolific-study-link.png +0 -0
  80. package/docs/img/visual_search_example.jpg +0 -0
  81. package/docs/index.md +0 -9
  82. package/docs/overview/browser-device-support.md +0 -35
  83. package/docs/overview/callbacks.md +0 -184
  84. package/docs/overview/data.md +0 -281
  85. package/docs/overview/dynamic-parameters.md +0 -147
  86. package/docs/overview/exclude-browser.md +0 -32
  87. package/docs/overview/experiment-options.md +0 -149
  88. package/docs/overview/eye-tracking.md +0 -237
  89. package/docs/overview/fullscreen.md +0 -36
  90. package/docs/overview/media-preloading.md +0 -369
  91. package/docs/overview/mturk.md +0 -77
  92. package/docs/overview/plugins.md +0 -320
  93. package/docs/overview/progress-bar.md +0 -110
  94. package/docs/overview/prolific.md +0 -78
  95. package/docs/overview/record-browser-interactions.md +0 -23
  96. package/docs/overview/running-experiments.md +0 -95
  97. package/docs/overview/style.md +0 -293
  98. package/docs/overview/timeline.md +0 -457
  99. package/docs/plugins/jspsych-animation.md +0 -40
  100. package/docs/plugins/jspsych-audio-button-response.md +0 -60
  101. package/docs/plugins/jspsych-audio-keyboard-response.md +0 -58
  102. package/docs/plugins/jspsych-audio-slider-response.md +0 -53
  103. package/docs/plugins/jspsych-call-function.md +0 -81
  104. package/docs/plugins/jspsych-canvas-button-response.md +0 -66
  105. package/docs/plugins/jspsych-canvas-keyboard-response.md +0 -68
  106. package/docs/plugins/jspsych-canvas-slider-response.md +0 -89
  107. package/docs/plugins/jspsych-categorize-animation.md +0 -60
  108. package/docs/plugins/jspsych-categorize-html.md +0 -53
  109. package/docs/plugins/jspsych-categorize-image.md +0 -53
  110. package/docs/plugins/jspsych-cloze.md +0 -45
  111. package/docs/plugins/jspsych-external-html.md +0 -70
  112. package/docs/plugins/jspsych-free-sort.md +0 -56
  113. package/docs/plugins/jspsych-fullscreen.md +0 -57
  114. package/docs/plugins/jspsych-html-button-response.md +0 -42
  115. package/docs/plugins/jspsych-html-keyboard-response.md +0 -51
  116. package/docs/plugins/jspsych-html-slider-response.md +0 -45
  117. package/docs/plugins/jspsych-iat-html.md +0 -64
  118. package/docs/plugins/jspsych-iat-image.md +0 -64
  119. package/docs/plugins/jspsych-image-button-response.md +0 -48
  120. package/docs/plugins/jspsych-image-keyboard-response.md +0 -58
  121. package/docs/plugins/jspsych-image-slider-response.md +0 -54
  122. package/docs/plugins/jspsych-instructions.md +0 -58
  123. package/docs/plugins/jspsych-maxdiff.md +0 -41
  124. package/docs/plugins/jspsych-preload.md +0 -128
  125. package/docs/plugins/jspsych-rdk.md +0 -119
  126. package/docs/plugins/jspsych-reconstruction.md +0 -48
  127. package/docs/plugins/jspsych-resize.md +0 -39
  128. package/docs/plugins/jspsych-same-different-html.md +0 -53
  129. package/docs/plugins/jspsych-same-different-image.md +0 -66
  130. package/docs/plugins/jspsych-serial-reaction-time-mouse.md +0 -52
  131. package/docs/plugins/jspsych-serial-reaction-time.md +0 -57
  132. package/docs/plugins/jspsych-survey-html-form.md +0 -50
  133. package/docs/plugins/jspsych-survey-likert.md +0 -70
  134. package/docs/plugins/jspsych-survey-multi-choice.md +0 -48
  135. package/docs/plugins/jspsych-survey-multi-select.md +0 -53
  136. package/docs/plugins/jspsych-survey-text.md +0 -63
  137. package/docs/plugins/jspsych-video-button-response.md +0 -54
  138. package/docs/plugins/jspsych-video-keyboard-response.md +0 -50
  139. package/docs/plugins/jspsych-video-slider-response.md +0 -60
  140. package/docs/plugins/jspsych-virtual-chinrest.md +0 -105
  141. package/docs/plugins/jspsych-visual-search-circle.md +0 -52
  142. package/docs/plugins/jspsych-vsl-animate-occlusion.md +0 -55
  143. package/docs/plugins/jspsych-vsl-grid-scene.md +0 -62
  144. package/docs/plugins/jspsych-webgazer-calibrate.md +0 -60
  145. package/docs/plugins/jspsych-webgazer-init-camera.md +0 -31
  146. package/docs/plugins/jspsych-webgazer-validate.md +0 -43
  147. package/docs/plugins/list-of-plugins.md +0 -54
  148. package/docs/tutorials/hello-world.md +0 -162
  149. package/docs/tutorials/rt-task.md +0 -1334
  150. package/docs/tutorials/video-tutorials.md +0 -11
  151. package/examples/add-to-end-of-timeline.html +0 -38
  152. package/examples/case-sensitive-responses.html +0 -45
  153. package/examples/conditional-and-loop-functions.html +0 -64
  154. package/examples/css/jquery-ui.css +0 -1225
  155. package/examples/css-classes-parameter.html +0 -145
  156. package/examples/data-add-properties.html +0 -44
  157. package/examples/data-as-function.html +0 -39
  158. package/examples/data-from-timeline.html +0 -52
  159. package/examples/data-from-url.html +0 -21
  160. package/examples/demo-flanker.html +0 -117
  161. package/examples/demo-simple-rt-task.html +0 -120
  162. package/examples/demos/demo_1.html +0 -35
  163. package/examples/demos/demo_2.html +0 -50
  164. package/examples/demos/demo_3.html +0 -63
  165. package/examples/display-element-to-embed-experiment.html +0 -79
  166. package/examples/end-active-node.html +0 -52
  167. package/examples/end-experiment.html +0 -45
  168. package/examples/exclusions.html +0 -32
  169. package/examples/external_html/simple_consent.html +0 -4
  170. package/examples/img/1.gif +0 -0
  171. package/examples/img/10.gif +0 -0
  172. package/examples/img/11.gif +0 -0
  173. package/examples/img/12.gif +0 -0
  174. package/examples/img/2.gif +0 -0
  175. package/examples/img/3.gif +0 -0
  176. package/examples/img/4.gif +0 -0
  177. package/examples/img/5.gif +0 -0
  178. package/examples/img/6.gif +0 -0
  179. package/examples/img/7.gif +0 -0
  180. package/examples/img/8.gif +0 -0
  181. package/examples/img/9.gif +0 -0
  182. package/examples/img/age/of1.jpg +0 -0
  183. package/examples/img/age/of2.jpg +0 -0
  184. package/examples/img/age/of3.jpg +0 -0
  185. package/examples/img/age/om1.jpg +0 -0
  186. package/examples/img/age/om2.jpg +0 -0
  187. package/examples/img/age/om3.jpg +0 -0
  188. package/examples/img/age/yf1.jpg +0 -0
  189. package/examples/img/age/yf4.jpg +0 -0
  190. package/examples/img/age/yf5.jpg +0 -0
  191. package/examples/img/age/ym2.jpg +0 -0
  192. package/examples/img/age/ym3.jpg +0 -0
  193. package/examples/img/age/ym5.jpg +0 -0
  194. package/examples/img/backwardN.gif +0 -0
  195. package/examples/img/blue.png +0 -0
  196. package/examples/img/card.png +0 -0
  197. package/examples/img/con1.png +0 -0
  198. package/examples/img/con2.png +0 -0
  199. package/examples/img/fixation.gif +0 -0
  200. package/examples/img/happy_face_1.jpg +0 -0
  201. package/examples/img/happy_face_2.jpg +0 -0
  202. package/examples/img/happy_face_3.jpg +0 -0
  203. package/examples/img/happy_face_4.jpg +0 -0
  204. package/examples/img/inc1.png +0 -0
  205. package/examples/img/inc2.png +0 -0
  206. package/examples/img/normalN.gif +0 -0
  207. package/examples/img/orange.png +0 -0
  208. package/examples/img/redX.png +0 -0
  209. package/examples/img/ribbon.jpg +0 -0
  210. package/examples/img/sad_face_1.jpg +0 -0
  211. package/examples/img/sad_face_2.jpg +0 -0
  212. package/examples/img/sad_face_3.jpg +0 -0
  213. package/examples/img/sad_face_4.jpg +0 -0
  214. package/examples/js/snap.svg-min.js +0 -21
  215. package/examples/js/webgazer.js +0 -88886
  216. package/examples/jspsych-RDK.html +0 -58
  217. package/examples/jspsych-animation.html +0 -39
  218. package/examples/jspsych-audio-button-response.html +0 -58
  219. package/examples/jspsych-audio-keyboard-response.html +0 -68
  220. package/examples/jspsych-audio-slider-response.html +0 -61
  221. package/examples/jspsych-call-function.html +0 -32
  222. package/examples/jspsych-canvas-button-response.html +0 -95
  223. package/examples/jspsych-canvas-keyboard-response.html +0 -78
  224. package/examples/jspsych-canvas-slider-response.html +0 -67
  225. package/examples/jspsych-categorize-animation.html +0 -49
  226. package/examples/jspsych-categorize-html.html +0 -33
  227. package/examples/jspsych-categorize-image.html +0 -44
  228. package/examples/jspsych-cloze.html +0 -37
  229. package/examples/jspsych-free-sort.html +0 -109
  230. package/examples/jspsych-fullscreen.html +0 -45
  231. package/examples/jspsych-html-button-response.html +0 -43
  232. package/examples/jspsych-html-keyboard-response.html +0 -42
  233. package/examples/jspsych-html-slider-response.html +0 -53
  234. package/examples/jspsych-iat.html +0 -520
  235. package/examples/jspsych-image-button-response.html +0 -91
  236. package/examples/jspsych-image-keyboard-response.html +0 -85
  237. package/examples/jspsych-image-slider-response.html +0 -85
  238. package/examples/jspsych-instructions.html +0 -37
  239. package/examples/jspsych-maxdiff.html +0 -33
  240. package/examples/jspsych-preload.html +0 -140
  241. package/examples/jspsych-reconstruction.html +0 -43
  242. package/examples/jspsych-resize.html +0 -34
  243. package/examples/jspsych-same-different-html.html +0 -28
  244. package/examples/jspsych-same-different-image.html +0 -39
  245. package/examples/jspsych-serial-reaction-time-mouse.html +0 -98
  246. package/examples/jspsych-serial-reaction-time.html +0 -54
  247. package/examples/jspsych-survey-html-form.html +0 -33
  248. package/examples/jspsych-survey-likert.html +0 -42
  249. package/examples/jspsych-survey-multi-choice.html +0 -40
  250. package/examples/jspsych-survey-multi-select.html +0 -42
  251. package/examples/jspsych-survey-text.html +0 -34
  252. package/examples/jspsych-video-button-response.html +0 -65
  253. package/examples/jspsych-video-keyboard-response.html +0 -61
  254. package/examples/jspsych-video-slider-response.html +0 -63
  255. package/examples/jspsych-virtual-chinrest.html +0 -69
  256. package/examples/jspsych-visual-search-circle.html +0 -64
  257. package/examples/jspsych-vsl-animate-occlusion.html +0 -35
  258. package/examples/jspsych-vsl-grid-scene.html +0 -47
  259. package/examples/lexical-decision.html +0 -134
  260. package/examples/manual-preloading.html +0 -59
  261. package/examples/pause-unpause.html +0 -33
  262. package/examples/progress-bar.html +0 -68
  263. package/examples/save-trial-parameters.html +0 -98
  264. package/examples/sound/hammer.mp3 +0 -0
  265. package/examples/sound/sound.mp3 +0 -0
  266. package/examples/sound/speech_blue.mp3 +0 -0
  267. package/examples/sound/speech_green.mp3 +0 -0
  268. package/examples/sound/speech_joke.mp3 +0 -0
  269. package/examples/sound/speech_red.mp3 +0 -0
  270. package/examples/sound/tone.mp3 +0 -0
  271. package/examples/timeline-variables-sampling.html +0 -50
  272. package/examples/timeline-variables.html +0 -64
  273. package/examples/video/sample_video.mp4 +0 -0
  274. package/examples/webgazer.html +0 -162
  275. package/examples/webgazer_image.html +0 -60
  276. package/extensions/jspsych-ext-webgazer.js +0 -185
  277. package/jspsych.js +0 -3015
  278. package/license.txt +0 -21
  279. package/mkdocs.yml +0 -118
  280. package/plugins/jspsych-animation.js +0 -189
  281. package/plugins/jspsych-audio-button-response.js +0 -269
  282. package/plugins/jspsych-audio-keyboard-response.js +0 -212
  283. package/plugins/jspsych-audio-slider-response.js +0 -278
  284. package/plugins/jspsych-call-function.js +0 -58
  285. package/plugins/jspsych-canvas-button-response.js +0 -199
  286. package/plugins/jspsych-canvas-keyboard-response.js +0 -155
  287. package/plugins/jspsych-canvas-slider-response.js +0 -207
  288. package/plugins/jspsych-categorize-animation.js +0 -266
  289. package/plugins/jspsych-categorize-html.js +0 -220
  290. package/plugins/jspsych-categorize-image.js +0 -222
  291. package/plugins/jspsych-cloze.js +0 -112
  292. package/plugins/jspsych-external-html.js +0 -112
  293. package/plugins/jspsych-free-sort.js +0 -478
  294. package/plugins/jspsych-fullscreen.js +0 -106
  295. package/plugins/jspsych-html-button-response.js +0 -188
  296. package/plugins/jspsych-html-keyboard-response.js +0 -149
  297. package/plugins/jspsych-html-slider-response.js +0 -202
  298. package/plugins/jspsych-iat-html.js +0 -284
  299. package/plugins/jspsych-iat-image.js +0 -286
  300. package/plugins/jspsych-image-button-response.js +0 -327
  301. package/plugins/jspsych-image-keyboard-response.js +0 -263
  302. package/plugins/jspsych-image-slider-response.js +0 -369
  303. package/plugins/jspsych-instructions.js +0 -237
  304. package/plugins/jspsych-maxdiff.js +0 -173
  305. package/plugins/jspsych-preload.js +0 -345
  306. package/plugins/jspsych-rdk.js +0 -1373
  307. package/plugins/jspsych-reconstruction.js +0 -134
  308. package/plugins/jspsych-resize.js +0 -166
  309. package/plugins/jspsych-same-different-html.js +0 -168
  310. package/plugins/jspsych-same-different-image.js +0 -169
  311. package/plugins/jspsych-serial-reaction-time-mouse.js +0 -212
  312. package/plugins/jspsych-serial-reaction-time.js +0 -247
  313. package/plugins/jspsych-survey-html-form.js +0 -171
  314. package/plugins/jspsych-survey-likert.js +0 -195
  315. package/plugins/jspsych-survey-multi-choice.js +0 -208
  316. package/plugins/jspsych-survey-multi-select.js +0 -232
  317. package/plugins/jspsych-survey-text.js +0 -185
  318. package/plugins/jspsych-video-button-response.js +0 -335
  319. package/plugins/jspsych-video-keyboard-response.js +0 -279
  320. package/plugins/jspsych-video-slider-response.js +0 -351
  321. package/plugins/jspsych-virtual-chinrest.js +0 -471
  322. package/plugins/jspsych-visual-search-circle.js +0 -259
  323. package/plugins/jspsych-vsl-animate-occlusion.js +0 -196
  324. package/plugins/jspsych-vsl-grid-scene.js +0 -103
  325. package/plugins/jspsych-webgazer-calibrate.js +0 -166
  326. package/plugins/jspsych-webgazer-init-camera.js +0 -95
  327. package/plugins/jspsych-webgazer-validate.js +0 -304
  328. package/plugins/template/jspsych-plugin-template.js +0 -35
  329. package/tests/README.md +0 -7
  330. package/tests/jsPsych/case-sensitive-responses.test.js +0 -53
  331. package/tests/jsPsych/css-classes-parameter.test.js +0 -107
  332. package/tests/jsPsych/default-iti.test.js +0 -51
  333. package/tests/jsPsych/default-parameters.test.js +0 -58
  334. package/tests/jsPsych/endexperiment.test.js +0 -49
  335. package/tests/jsPsych/events.test.js +0 -606
  336. package/tests/jsPsych/functions-as-parameters.test.js +0 -210
  337. package/tests/jsPsych/init.test.js +0 -48
  338. package/tests/jsPsych/loads.test.js +0 -7
  339. package/tests/jsPsych/min-rt.test.js +0 -58
  340. package/tests/jsPsych/progressbar.test.js +0 -202
  341. package/tests/jsPsych/timeline-variables.test.js +0 -531
  342. package/tests/jsPsych/timelines.test.js +0 -569
  343. package/tests/jsPsych.data/data-csv-conversion.test.js +0 -85
  344. package/tests/jsPsych.data/data-json-conversion.test.js +0 -120
  345. package/tests/jsPsych.data/datacollection.test.js +0 -117
  346. package/tests/jsPsych.data/datacolumn.test.js +0 -50
  347. package/tests/jsPsych.data/datamodule.test.js +0 -152
  348. package/tests/jsPsych.data/dataparameter.test.js +0 -251
  349. package/tests/jsPsych.data/interactions.test.js +0 -109
  350. package/tests/jsPsych.data/trialparameters.test.js +0 -175
  351. package/tests/jsPsych.extensions/extensions.test.js +0 -207
  352. package/tests/jsPsych.extensions/test-extension.js +0 -42
  353. package/tests/jsPsych.pluginAPI/pluginapi.test.js +0 -341
  354. package/tests/jsPsych.pluginAPI/preloads.test.js +0 -43
  355. package/tests/jsPsych.randomization/randomziation.test.js +0 -27
  356. package/tests/jsPsych.utils/utils.test.js +0 -58
  357. package/tests/plugins/plugin-animation.test.js +0 -34
  358. package/tests/plugins/plugin-audio-button-response.test.js +0 -15
  359. package/tests/plugins/plugin-audio-keyboard-response.test.js +0 -15
  360. package/tests/plugins/plugin-audio-slider-response.test.js +0 -15
  361. package/tests/plugins/plugin-call-function.test.js +0 -49
  362. package/tests/plugins/plugin-categorize-animation.test.js +0 -263
  363. package/tests/plugins/plugin-categorize-html.test.js +0 -17
  364. package/tests/plugins/plugin-categorize-image.test.js +0 -17
  365. package/tests/plugins/plugin-cloze.test.js +0 -157
  366. package/tests/plugins/plugin-free-sort.test.js +0 -106
  367. package/tests/plugins/plugin-fullscreen.test.js +0 -41
  368. package/tests/plugins/plugin-html-button-response.test.js +0 -161
  369. package/tests/plugins/plugin-html-keyboard-response.test.js +0 -139
  370. package/tests/plugins/plugin-html-slider-response.test.js +0 -155
  371. package/tests/plugins/plugin-iat-html.test.js +0 -299
  372. package/tests/plugins/plugin-iat-image.test.js +0 -298
  373. package/tests/plugins/plugin-image-button-response.test.js +0 -174
  374. package/tests/plugins/plugin-image-keyboard-response.test.js +0 -147
  375. package/tests/plugins/plugin-image-slider-response.test.js +0 -174
  376. package/tests/plugins/plugin-instructions.test.js +0 -85
  377. package/tests/plugins/plugin-maxdiff.test.js +0 -39
  378. package/tests/plugins/plugin-preload.test.js +0 -916
  379. package/tests/plugins/plugin-rdk.test.js +0 -61
  380. package/tests/plugins/plugin-reconstruction.test.js +0 -16
  381. package/tests/plugins/plugin-resize.test.js +0 -16
  382. package/tests/plugins/plugin-same-different-html.test.js +0 -17
  383. package/tests/plugins/plugin-same-different-image.test.js +0 -17
  384. package/tests/plugins/plugin-serial-reaction-time-mouse.test.js +0 -42
  385. package/tests/plugins/plugin-serial-reaction-time.test.js +0 -109
  386. package/tests/plugins/plugin-survey-html-form.test.js +0 -44
  387. package/tests/plugins/plugin-survey-likert.test.js +0 -48
  388. package/tests/plugins/plugin-survey-multi-choice.test.js +0 -47
  389. package/tests/plugins/plugin-survey-multi-select.test.js +0 -71
  390. package/tests/plugins/plugin-survey-text.test.js +0 -115
  391. package/tests/plugins/plugin-video-button-response.test.js +0 -32
  392. package/tests/plugins/plugin-video-keyboard-response.test.js +0 -32
  393. package/tests/plugins/plugin-video-slider-response.test.js +0 -31
  394. package/tests/plugins/plugin-visual-search-circle.test.js +0 -16
  395. package/tests/plugins/plugin-vsl-animate-occlusion.test.js +0 -16
  396. package/tests/plugins/plugin-vsl-grid-scene.test.js +0 -16
  397. package/tests/testing-utils.js +0 -13
@@ -0,0 +1,28 @@
1
+ import autoBind from "auto-bind";
2
+
3
+ import { JsPsych } from "../../JsPsych";
4
+ import { HardwareAPI } from "./HardwareAPI";
5
+ import { KeyboardListenerAPI } from "./KeyboardListenerAPI";
6
+ import { MediaAPI } from "./MediaAPI";
7
+ import { SimulationAPI } from "./SimulationAPI";
8
+ import { TimeoutAPI } from "./TimeoutAPI";
9
+
10
+ export function createJointPluginAPIObject(jsPsych: JsPsych) {
11
+ const settings = jsPsych.getInitSettings();
12
+ return Object.assign(
13
+ {},
14
+ ...[
15
+ new KeyboardListenerAPI(
16
+ jsPsych.getDisplayContainerElement,
17
+ settings.case_sensitive_responses,
18
+ settings.minimum_valid_rt
19
+ ),
20
+ new TimeoutAPI(),
21
+ new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context),
22
+ new HardwareAPI(),
23
+ new SimulationAPI(),
24
+ ].map((object) => autoBind(object))
25
+ ) as KeyboardListenerAPI & TimeoutAPI & MediaAPI & HardwareAPI & SimulationAPI;
26
+ }
27
+
28
+ export type PluginAPI = ReturnType<typeof createJointPluginAPIObject>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ Flatten the type output to improve type hints shown in editors.
3
+ Borrowed from type-fest
4
+ */
5
+ type Simplify<T> = { [KeyType in keyof T]: T[KeyType] };
6
+
7
+ /**
8
+ Create a type that makes the given keys required. The remaining keys are kept as is.
9
+ Borrowed from type-fest
10
+ */
11
+ type SetRequired<BaseType, Keys extends keyof BaseType> = Simplify<
12
+ Omit<BaseType, Keys> & Required<Pick<BaseType, Keys>>
13
+ >;
14
+
15
+ /**
16
+ * Parameter types for plugins
17
+ */
18
+ export enum ParameterType {
19
+ BOOL,
20
+ STRING,
21
+ INT,
22
+ FLOAT,
23
+ FUNCTION,
24
+ KEY,
25
+ KEYS,
26
+ SELECT,
27
+ HTML_STRING,
28
+ IMAGE,
29
+ AUDIO,
30
+ VIDEO,
31
+ OBJECT,
32
+ COMPLEX,
33
+ TIMELINE,
34
+ }
35
+
36
+ type ParameterTypeMap = {
37
+ [ParameterType.BOOL]: boolean;
38
+ [ParameterType.STRING]: string;
39
+ [ParameterType.INT]: number;
40
+ [ParameterType.FLOAT]: number;
41
+ [ParameterType.FUNCTION]: (...args: any[]) => any;
42
+ [ParameterType.KEY]: string;
43
+ [ParameterType.KEYS]: string[] | "ALL_KEYS" | "NO_KEYS";
44
+ [ParameterType.SELECT]: any;
45
+ [ParameterType.HTML_STRING]: string;
46
+ [ParameterType.IMAGE]: string;
47
+ [ParameterType.AUDIO]: string;
48
+ [ParameterType.VIDEO]: string;
49
+ [ParameterType.OBJECT]: object;
50
+ [ParameterType.COMPLEX]: any;
51
+ [ParameterType.TIMELINE]: any;
52
+ };
53
+
54
+ export interface ParameterInfo {
55
+ type: ParameterType;
56
+ array?: boolean;
57
+ pretty_name?: string;
58
+ default?: any;
59
+ preload?: boolean;
60
+ }
61
+
62
+ export interface ParameterInfos {
63
+ [key: string]: ParameterInfo;
64
+ }
65
+
66
+ type InferredParameter<I extends ParameterInfo> = I["array"] extends true
67
+ ? Array<ParameterTypeMap[I["type"]]>
68
+ : ParameterTypeMap[I["type"]];
69
+
70
+ type RequiredParameterNames<I extends ParameterInfos> = {
71
+ [K in keyof I]: I[K]["default"] extends undefined ? K : never;
72
+ }[keyof I];
73
+
74
+ type InferredParameters<I extends ParameterInfos> = SetRequired<
75
+ {
76
+ [Property in keyof I]?: InferredParameter<I[Property]>;
77
+ },
78
+ RequiredParameterNames<I>
79
+ >;
80
+
81
+ export const universalPluginParameters = <const>{
82
+ /**
83
+ * Data to add to this trial (key-value pairs)
84
+ */
85
+ data: {
86
+ type: ParameterType.OBJECT,
87
+ pretty_name: "Data",
88
+ default: {},
89
+ },
90
+ /**
91
+ * Function to execute when trial begins
92
+ */
93
+ on_start: {
94
+ type: ParameterType.FUNCTION,
95
+ pretty_name: "On start",
96
+ default: function () {
97
+ return;
98
+ },
99
+ },
100
+ /**
101
+ * Function to execute when trial is finished
102
+ */
103
+ on_finish: {
104
+ type: ParameterType.FUNCTION,
105
+ pretty_name: "On finish",
106
+ default: function () {
107
+ return;
108
+ },
109
+ },
110
+ /**
111
+ * Function to execute after the trial has loaded
112
+ */
113
+ on_load: {
114
+ type: ParameterType.FUNCTION,
115
+ pretty_name: "On load",
116
+ default: function () {
117
+ return;
118
+ },
119
+ },
120
+ /**
121
+ * Length of gap between the end of this trial and the start of the next trial
122
+ */
123
+ post_trial_gap: {
124
+ type: ParameterType.INT,
125
+ pretty_name: "Post trial gap",
126
+ default: null,
127
+ },
128
+ /**
129
+ * A list of CSS classes to add to the jsPsych display element for the duration of this trial
130
+ */
131
+ css_classes: {
132
+ type: ParameterType.STRING,
133
+ pretty_name: "Custom CSS classes",
134
+ default: null,
135
+ },
136
+ };
137
+
138
+ export type UniversalPluginParameters = InferredParameters<typeof universalPluginParameters>;
139
+
140
+ export interface PluginInfo {
141
+ name: string;
142
+ parameters: {
143
+ [key: string]: ParameterInfo;
144
+ };
145
+ }
146
+
147
+ export interface JsPsychPlugin<I extends PluginInfo> {
148
+ trial(
149
+ display_element: HTMLElement,
150
+ trial: TrialType<I>,
151
+ on_load?: () => void
152
+ ): void | Promise<any>;
153
+ }
154
+
155
+ export type TrialType<I extends PluginInfo> = InferredParameters<I["parameters"]> &
156
+ UniversalPluginParameters;
157
+
158
+ export type PluginParameters<I extends PluginInfo> = InferredParameters<I["parameters"]>;
@@ -0,0 +1,327 @@
1
+ import rw from "random-words";
2
+
3
+ export function repeat(array, repetitions, unpack = false) {
4
+ const arr_isArray = Array.isArray(array);
5
+ const rep_isArray = Array.isArray(repetitions);
6
+
7
+ // if array is not an array, then we just repeat the item
8
+ if (!arr_isArray) {
9
+ if (!rep_isArray) {
10
+ array = [array];
11
+ repetitions = [repetitions];
12
+ } else {
13
+ repetitions = [repetitions[0]];
14
+ console.log(
15
+ "Unclear parameters given to randomization.repeat. Multiple set sizes specified, but only one item exists to sample. Proceeding using the first set size."
16
+ );
17
+ }
18
+ } else {
19
+ // if repetitions is not an array, but array is, then we
20
+ // repeat repetitions for each entry in array
21
+ if (!rep_isArray) {
22
+ let reps = [];
23
+ for (let i = 0; i < array.length; i++) {
24
+ reps.push(repetitions);
25
+ }
26
+ repetitions = reps;
27
+ } else {
28
+ if (array.length != repetitions.length) {
29
+ console.warn(
30
+ "Unclear parameters given to randomization.repeat. Items and repetitions are unequal lengths. Behavior may not be as expected."
31
+ );
32
+ // throw warning if repetitions is too short, use first rep ONLY.
33
+ if (repetitions.length < array.length) {
34
+ let reps = [];
35
+ for (let i = 0; i < array.length; i++) {
36
+ reps.push(repetitions);
37
+ }
38
+ repetitions = reps;
39
+ } else {
40
+ // throw warning if too long, and then use the first N
41
+ repetitions = repetitions.slice(0, array.length);
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ // should be clear at this point to assume that array and repetitions are arrays with == length
48
+ let allsamples = [];
49
+ for (let i = 0; i < array.length; i++) {
50
+ for (let j = 0; j < repetitions[i]; j++) {
51
+ if (array[i] == null || typeof array[i] != "object") {
52
+ allsamples.push(array[i]);
53
+ } else {
54
+ allsamples.push(Object.assign({}, array[i]));
55
+ }
56
+ }
57
+ }
58
+
59
+ let out: any = shuffle(allsamples);
60
+
61
+ if (unpack) {
62
+ out = unpackArray(out);
63
+ }
64
+
65
+ return out;
66
+ }
67
+
68
+ export function shuffle(array: Array<any>) {
69
+ if (!Array.isArray(array)) {
70
+ console.error("Argument to shuffle() must be an array.");
71
+ }
72
+
73
+ const copy_array = array.slice(0);
74
+ let m = copy_array.length,
75
+ t,
76
+ i;
77
+
78
+ // While there remain elements to shuffle…
79
+ while (m) {
80
+ // Pick a remaining element…
81
+ i = Math.floor(Math.random() * m--);
82
+
83
+ // And swap it with the current element.
84
+ t = copy_array[m];
85
+ copy_array[m] = copy_array[i];
86
+ copy_array[i] = t;
87
+ }
88
+
89
+ return copy_array;
90
+ }
91
+
92
+ export function shuffleNoRepeats(arr: Array<any>, equalityTest: (a: any, b: any) => boolean) {
93
+ if (!Array.isArray(arr)) {
94
+ console.error("First argument to shuffleNoRepeats() must be an array.");
95
+ }
96
+ if (typeof equalityTest !== "undefined" && typeof equalityTest !== "function") {
97
+ console.error("Second argument to shuffleNoRepeats() must be a function.");
98
+ }
99
+ // define a default equalityTest
100
+ if (typeof equalityTest == "undefined") {
101
+ equalityTest = function (a, b) {
102
+ if (a === b) {
103
+ return true;
104
+ } else {
105
+ return false;
106
+ }
107
+ };
108
+ }
109
+
110
+ const random_shuffle = shuffle(arr);
111
+ for (let i = 0; i < random_shuffle.length - 1; i++) {
112
+ if (equalityTest(random_shuffle[i], random_shuffle[i + 1])) {
113
+ // neighbors are equal, pick a new random neighbor to swap (not the first or last element, to avoid edge cases)
114
+ let random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
115
+ // test to make sure the new neighbor isn't equal to the old one
116
+ while (
117
+ equalityTest(random_shuffle[i + 1], random_shuffle[random_pick]) ||
118
+ equalityTest(random_shuffle[i + 1], random_shuffle[random_pick + 1]) ||
119
+ equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1])
120
+ ) {
121
+ random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
122
+ }
123
+ const new_neighbor = random_shuffle[random_pick];
124
+ random_shuffle[random_pick] = random_shuffle[i + 1];
125
+ random_shuffle[i + 1] = new_neighbor;
126
+ }
127
+ }
128
+
129
+ return random_shuffle;
130
+ }
131
+
132
+ export function shuffleAlternateGroups(arr_groups, random_group_order = false) {
133
+ const n_groups = arr_groups.length;
134
+ if (n_groups == 1) {
135
+ console.warn(
136
+ "shuffleAlternateGroups() was called with only one group. Defaulting to simple shuffle."
137
+ );
138
+ return shuffle(arr_groups[0]);
139
+ }
140
+
141
+ let group_order = [];
142
+ for (let i = 0; i < n_groups; i++) {
143
+ group_order.push(i);
144
+ }
145
+ if (random_group_order) {
146
+ group_order = shuffle(group_order);
147
+ }
148
+
149
+ const randomized_groups = [];
150
+ let min_length = null;
151
+ for (let i = 0; i < n_groups; i++) {
152
+ min_length =
153
+ min_length === null ? arr_groups[i].length : Math.min(min_length, arr_groups[i].length);
154
+ randomized_groups.push(shuffle(arr_groups[i]));
155
+ }
156
+
157
+ const out = [];
158
+ for (let i = 0; i < min_length; i++) {
159
+ for (let j = 0; j < group_order.length; j++) {
160
+ out.push(randomized_groups[group_order[j]][i]);
161
+ }
162
+ }
163
+
164
+ return out;
165
+ }
166
+
167
+ export function sampleWithoutReplacement(arr, size) {
168
+ if (!Array.isArray(arr)) {
169
+ console.error("First argument to sampleWithoutReplacement() must be an array");
170
+ }
171
+
172
+ if (size > arr.length) {
173
+ console.error("Cannot take a sample larger than the size of the set of items to sample.");
174
+ }
175
+ return shuffle(arr).slice(0, size);
176
+ }
177
+
178
+ export function sampleWithReplacement(arr, size, weights?) {
179
+ if (!Array.isArray(arr)) {
180
+ console.error("First argument to sampleWithReplacement() must be an array");
181
+ }
182
+
183
+ const normalized_weights = [];
184
+ if (typeof weights !== "undefined") {
185
+ if (weights.length !== arr.length) {
186
+ console.error(
187
+ "The length of the weights array must equal the length of the array " +
188
+ "to be sampled from."
189
+ );
190
+ }
191
+ let weight_sum = 0;
192
+ for (const weight of weights) {
193
+ weight_sum += weight;
194
+ }
195
+ for (const weight of weights) {
196
+ normalized_weights.push(weight / weight_sum);
197
+ }
198
+ } else {
199
+ for (let i = 0; i < arr.length; i++) {
200
+ normalized_weights.push(1 / arr.length);
201
+ }
202
+ }
203
+
204
+ const cumulative_weights = [normalized_weights[0]];
205
+ for (let i = 1; i < normalized_weights.length; i++) {
206
+ cumulative_weights.push(normalized_weights[i] + cumulative_weights[i - 1]);
207
+ }
208
+
209
+ const samp = [];
210
+ for (let i = 0; i < size; i++) {
211
+ const rnd = Math.random();
212
+ let index = 0;
213
+ while (rnd > cumulative_weights[index]) {
214
+ index++;
215
+ }
216
+ samp.push(arr[index]);
217
+ }
218
+ return samp;
219
+ }
220
+
221
+ export function factorial(factors: Record<string, any>, repetitions = 1, unpack = false) {
222
+ let design = [{}];
223
+ for (const [factorName, factor] of Object.entries(factors)) {
224
+ const new_design = [];
225
+ for (const level of factor) {
226
+ for (const cell of design) {
227
+ new_design.push({ ...cell, [factorName]: level });
228
+ }
229
+ }
230
+ design = new_design;
231
+ }
232
+
233
+ return repeat(design, repetitions, unpack);
234
+ }
235
+
236
+ export function randomID(length = 32) {
237
+ let result = "";
238
+ const chars = "0123456789abcdefghjklmnopqrstuvwxyz";
239
+ for (let i = 0; i < length; i++) {
240
+ result += chars[Math.floor(Math.random() * chars.length)];
241
+ }
242
+ return result;
243
+ }
244
+
245
+ /**
246
+ * Generate a random integer from `lower` to `upper`, inclusive of both end points.
247
+ * @param lower The lowest value it is possible to generate
248
+ * @param upper The highest value it is possible to generate
249
+ * @returns A random integer
250
+ */
251
+ export function randomInt(lower: number, upper: number) {
252
+ if (upper < lower) {
253
+ throw new Error("Upper boundary must be less than or equal to lower boundary");
254
+ }
255
+ return lower + Math.floor(Math.random() * (upper - lower + 1));
256
+ }
257
+
258
+ /**
259
+ * Generates a random sample from a Bernoulli distribution.
260
+ * @param p The probability of sampling 1.
261
+ * @returns 0, with probability 1-p, or 1, with probability p.
262
+ */
263
+ export function sampleBernoulli(p: number) {
264
+ return Math.random() <= p ? 1 : 0;
265
+ }
266
+
267
+ export function sampleNormal(mean: number, standard_deviation: number) {
268
+ return randn_bm() * standard_deviation + mean;
269
+ }
270
+
271
+ export function sampleExponential(rate: number) {
272
+ return -Math.log(Math.random()) / rate;
273
+ }
274
+
275
+ export function sampleExGaussian(
276
+ mean: number,
277
+ standard_deviation: number,
278
+ rate: number,
279
+ positive = false
280
+ ) {
281
+ let s = sampleNormal(mean, standard_deviation) + sampleExponential(rate);
282
+ if (positive) {
283
+ while (s <= 0) {
284
+ s = sampleNormal(mean, standard_deviation) + sampleExponential(rate);
285
+ }
286
+ }
287
+ return s;
288
+ }
289
+
290
+ /**
291
+ * Generate one or more random words.
292
+ *
293
+ * This is a wrapper function for the {@link https://www.npmjs.com/package/random-words `random-words` npm package}.
294
+ *
295
+ * @param opts An object with optional properties `min`, `max`, `exactly`,
296
+ * `join`, `maxLength`, `wordsPerString`, `separator`, and `formatter`.
297
+ *
298
+ * @returns An array of words or a single string, depending on parameter choices.
299
+ */
300
+ export function randomWords(opts) {
301
+ return rw(opts);
302
+ }
303
+
304
+ // Box-Muller transformation for a random sample from normal distribution with mean = 0, std = 1
305
+ // https://stackoverflow.com/a/36481059/3726673
306
+ function randn_bm() {
307
+ var u = 0,
308
+ v = 0;
309
+ while (u === 0) u = Math.random(); //Converting [0,1) to (0,1)
310
+ while (v === 0) v = Math.random();
311
+ return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
312
+ }
313
+
314
+ function unpackArray(array) {
315
+ const out = {};
316
+
317
+ for (const x of array) {
318
+ for (const key of Object.keys(x)) {
319
+ if (typeof out[key] === "undefined") {
320
+ out[key] = [];
321
+ }
322
+ out[key].push(x[key]);
323
+ }
324
+ }
325
+
326
+ return out;
327
+ }
@@ -0,0 +1,99 @@
1
+ interface turkInformation {
2
+ /**
3
+ * Is the experiment being loaded in preview mode on Mechanical Turk?
4
+ */
5
+ previewMode: boolean;
6
+ /**
7
+ * Is the experiment being loaded outside of the Mechanical Turk environment?
8
+ */
9
+ outsideTurk: boolean;
10
+ /**
11
+ * The HIT ID.
12
+ */
13
+ hitId: string;
14
+ /**
15
+ * The Assignment ID.
16
+ */
17
+ assignmentId: string;
18
+ /**
19
+ * The worker ID.
20
+ */
21
+ workerId: string;
22
+ /**
23
+ * URL for submission of the HIT.
24
+ */
25
+ turkSubmitTo: string;
26
+ }
27
+
28
+ /**
29
+ * Gets information about the Mechanical Turk Environment, HIT, Assignment, and Worker
30
+ * by parsing the URL variables that Mechanical Turk generates.
31
+ * @returns An object containing information about the Mechanical Turk Environment, HIT, Assignment, and Worker.
32
+ */
33
+ export function turkInfo(): turkInformation {
34
+ const turk: turkInformation = {
35
+ previewMode: false,
36
+ outsideTurk: false,
37
+ hitId: "INVALID_URL_PARAMETER",
38
+ assignmentId: "INVALID_URL_PARAMETER",
39
+ workerId: "INVALID_URL_PARAMETER",
40
+ turkSubmitTo: "INVALID_URL_PARAMETER",
41
+ };
42
+
43
+ const param = function (url, name) {
44
+ name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
45
+ const regexS = "[\\?&]" + name + "=([^&#]*)";
46
+ const regex = new RegExp(regexS);
47
+ const results = regex.exec(url);
48
+ return results == null ? "" : results[1];
49
+ };
50
+
51
+ const src = param(window.location.href, "assignmentId")
52
+ ? window.location.href
53
+ : document.referrer;
54
+
55
+ const keys = ["assignmentId", "hitId", "workerId", "turkSubmitTo"];
56
+ keys.map(function (key) {
57
+ turk[key] = unescape(param(src, key));
58
+ });
59
+
60
+ turk.previewMode = turk.assignmentId == "ASSIGNMENT_ID_NOT_AVAILABLE";
61
+
62
+ turk.outsideTurk =
63
+ !turk.previewMode && turk.hitId === "" && turk.assignmentId == "" && turk.workerId == "";
64
+
65
+ return turk;
66
+ }
67
+
68
+ /**
69
+ * Send data to Mechnical Turk for storage.
70
+ * @param data An object containing `key:value` pairs to send to Mechanical Turk. Values
71
+ * cannot contain nested objects, arrays, or functions.
72
+ * @returns Nothing
73
+ */
74
+ export function submitToTurk(data) {
75
+ const turk = turkInfo();
76
+ const assignmentId = turk.assignmentId;
77
+ const turkSubmitTo = turk.turkSubmitTo;
78
+
79
+ if (!assignmentId || !turkSubmitTo) return;
80
+
81
+ const form = document.createElement("form");
82
+ form.method = "POST";
83
+ form.action = turkSubmitTo + "/mturk/externalSubmit?assignmentId=" + assignmentId;
84
+
85
+ for (const key in data) {
86
+ if (data.hasOwnProperty(key)) {
87
+ const hiddenField = document.createElement("input");
88
+ hiddenField.type = "hidden";
89
+ hiddenField.name = key;
90
+ hiddenField.id = key;
91
+ hiddenField.value = data[key];
92
+
93
+ form.appendChild(hiddenField);
94
+ }
95
+ }
96
+
97
+ document.body.appendChild(form);
98
+ form.submit();
99
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Finds all of the unique items in an array.
3
+ * @param arr The array to extract unique values from
4
+ * @returns An array with one copy of each unique item in `arr`
5
+ */
6
+ export function unique(arr: Array<any>) {
7
+ return [...new Set(arr)];
8
+ }
9
+
10
+ export function deepCopy(obj) {
11
+ if (!obj) return obj;
12
+ let out;
13
+ if (Array.isArray(obj)) {
14
+ out = [];
15
+ for (const x of obj) {
16
+ out.push(deepCopy(x));
17
+ }
18
+ return out;
19
+ } else if (typeof obj === "object" && obj !== null) {
20
+ out = {};
21
+ for (const key in obj) {
22
+ if (obj.hasOwnProperty(key)) {
23
+ out[key] = deepCopy(obj[key]);
24
+ }
25
+ }
26
+ return out;
27
+ } else {
28
+ return obj;
29
+ }
30
+ }
@@ -1,20 +0,0 @@
1
- # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2
- # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
-
4
- name: Jest Test
5
-
6
- on: [ push, pull_request ]
7
-
8
- jobs:
9
- build:
10
-
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - uses: actions/checkout@v2
15
- - name: Use Node.js
16
- uses: actions/setup-node@v1
17
- with:
18
- node-version: '14.x'
19
- - run: npm ci
20
- - run: npm test