From 3f3d984a4cc51a14c2a6bd499009e0620f95088c Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:58:44 -0500 Subject: [PATCH 01/25] fix up formatting errors --- docs/overview/browser-device-support.md | 11 ++++++----- docs/reference/jspsych.md | 2 ++ docs/support/migration-v8.md | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/overview/browser-device-support.md b/docs/overview/browser-device-support.md index 891e0c42ab..7de505a487 100644 --- a/docs/overview/browser-device-support.md +++ b/docs/overview/browser-device-support.md @@ -22,11 +22,12 @@ There are many other web browsers that are available, but that are not commonly In general, jsPsych experiments can be run on mobile devices (smartphones and tablets). However, certain plugins will not work on mobile. For instance, any plugin that requires a keyboard response without a text input box, such as the *-keyboard-response plugins, will not work. Even plugins that do work on mobile might work differently than they do on desktop and laptop computers. For instance, on mobile devices, a text input box will cause an on-screen keyboard to pop up, which affects the visible content on the screen. If you plan to run an experiment that allows people to use mobile devices, we recommend doing some extra testing to make sure that everything works as expected. In particular, you may want to check that: -* Font sizes are readable on smaller screens -* Stimuli sizes are large enough and appropriate for the task -* Page is laid out as intended (e.g. elements are centered and do not overlap) -* Response options are touchscreen-friendly (e.g. buttons rather than key presses) -* Response options (e.g. buttons, text boxes, radio buttons) are large enough and far enough apart to be easily selected with a finger tap + + * Font sizes are readable on smaller screens + * Stimuli sizes are large enough and appropriate for the task + * Page is laid out as intended (e.g. elements are centered and do not overlap) + * Response options are touchscreen-friendly (e.g. buttons rather than key presses) + * Response options (e.g. buttons, text boxes, radio buttons) are large enough and far enough apart to be easily selected with a finger tap It's possible to use your browser's developer tools to emulate mobile devices ([this page shows how to do it in Chrome](https://developers.google.com/web/tools/chrome-devtools/device-mode)), which is useful for getting a sense of how your experiment will look on mobile devices. Just be aware that there are limitations to emulator tools, and there are some aspects of mobile devices/browsers that a desktop browser will not be able to simulate. diff --git a/docs/reference/jspsych.md b/docs/reference/jspsych.md index 906241fc73..da29234b76 100644 --- a/docs/reference/jspsych.md +++ b/docs/reference/jspsych.md @@ -400,6 +400,8 @@ var el = jsPsych.getDisplayElement(); // hide the jsPsych display el.style.visibility = 'hidden'; ``` + + --- ## jsPsych.getInitSettings diff --git a/docs/support/migration-v8.md b/docs/support/migration-v8.md index b0651a1009..16a6049545 100644 --- a/docs/support/migration-v8.md +++ b/docs/support/migration-v8.md @@ -83,7 +83,7 @@ const trial = { ``` The `button_html` parameter can also support different HTML for each button. -See the [plugin documentation](https://www.jspsych.org/plugins/jspsych-html-button-response/) for more details. +See the [plugin documentation](https://www.jspsych.org/latest/plugins/html-button-response/index.html) for more details. ## Plugin parameter handling From 4b14995d4ac2fdb276806a909fb9a406d06913cb Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:39:32 -0500 Subject: [PATCH 02/25] fix audio stop description --- docs/reference/jspsych-pluginAPI.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/reference/jspsych-pluginAPI.md b/docs/reference/jspsych-pluginAPI.md index 46c782ea4c..ffc7da3b58 100644 --- a/docs/reference/jspsych-pluginAPI.md +++ b/docs/reference/jspsych-pluginAPI.md @@ -289,7 +289,7 @@ See the `audio-keyboard-response` plugin for an example in a fuller context. ```javascript const audio = jsPsych.pluginAPI.getAudioPlayer(filepath); -audio.play(); +audio.stop(); ``` #### Return value @@ -298,7 +298,7 @@ Returns nothing. #### Description -Method that belongs to the AudioPlayer class. Stops the audio loaded into the audio buffer of the AudioPlayer instance for a particular file. If the audio is an HTML5 audio object it pauses it. If the audio is a Webaudio API object it stops it. +Method that belongs to the AudioPlayer class. Stops the audio loaded into the audio buffer of the AudioPlayer instance for a particular file. If the audio is an HTML5 audio object it pauses it. If the audio is a Webaudio API object it stops it. This will regenerate the audio player, allowing you to call the `play()` method upon it again. #### Example @@ -310,7 +310,6 @@ const audio = await jsPsych.pluginAPI.getAudioPlayer('my-sound.mp3'); audio.play(); audio.stop(); - ``` See the `audio-keyboard-response` plugin for an example in a fuller context. From f8398601c31fa4c1a27db46a4e5388a6b22a13ad Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:11:41 -0500 Subject: [PATCH 03/25] add AudioPlayer v8 migration info --- docs/support/migration-v8.md | 49 +++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/support/migration-v8.md b/docs/support/migration-v8.md index 16a6049545..b5cd6071b4 100644 --- a/docs/support/migration-v8.md +++ b/docs/support/migration-v8.md @@ -2,7 +2,8 @@ Version 8.x of jsPsych focused on a complete rewrite of the core library to enable new features and make it easier to maintain. Most of the changes in version 8.x are behind the scenes. -However, there are some breaking changes that you will need to address in your experiment code in order to upgrade to v8.x. +However, there are some breaking changes that you will need to address in your experiment code in order to upgrade to v8.x. +If you are a plugin developer, there are also some special considerations below to factor in when developing your plugins or modifying existing ones. This guide is aimed at upgrades from version 7.x to 8.x. If you are using version 6.x or earlier, please follow the [migration guide for v7.x](./migration-v7.md) before trying to upgrade to v8.x. @@ -85,6 +86,9 @@ const trial = { The `button_html` parameter can also support different HTML for each button. See the [plugin documentation](https://www.jspsych.org/latest/plugins/html-button-response/index.html) for more details. +For plugin developers: if you are writing a plugin and updating parameters to use functions, +make sure to mock these functions in Jest to ensure tests can still run. + ## Plugin parameter handling In version 7.x, a plugin could omit parameters from the `info` object and jsPsych would still evaluate these parameters appropriately in most cases. @@ -104,6 +108,49 @@ Including these properties is not *required* for a plugin to work, but it is rec In version 8.x, jsPsych will throw a warning if a plugin is used that does not have a `version` or `data` property in the `info` object. In version 9.x, we plan to make this a requirement. +## Changes to the `AudioPlayer` class + +In version 7.x, jsPsych's `pluginAPI` class exposed WebAudio and HTML5 audio APIs through `getAudioBuffer()`. However, this required different implementations done by the developer to account for each API. +In version 8.x, we've removed this in favor of `getAudioPlayer()`, which handles both API choices under the hood. + +This change only effects plugin developers. If you want to update to use the new `getAudioPlayer()`, it is recommend that you call this new method using the `await` syntax, which requires an asyncrhonous `trial` function: +```js +const audio = await jsPsych.pluginAPI.getAudioPlayer('my-sound.mp3'); +``` + +If you'd like to still use the `.then()` syntax to resolve the Promise generated, you may update it as such: + +Version 7.x: +```js +this.jsPsych.pluginAPI + .getAudioBuffer('my-audio.mp3') + .then((audio) => { + // call play on audio if HTML5 audio API, create and connect buffer if WebAudio API + }) + .catch((err) =>{ + // handle error + }); +``` + +Version 8.x: +```js +this.jsPsych.pluginAPI + .getAudioPlayer('my-audio.mp3') + .then((player) => { + // no need to create and connect buffer, can just directly call functions on player + }) + .catch((err) => { + // handle error + }) +``` + +Along with this, the `start()` and `pause()` functions were removed from the `AudioPlayer` class. +You can still call `stop()` upon an audio ending in order to regenerate the `AudioPlayer`, and be able +to call `play()` on it again. + +For a general guide on implementation, the `audio-button-response` plugin uses the `await` syntax +to handle playing audio. + ## Changes to `finishTrial()` When a plugin calls `finishTrial()` or ends via a `return` statement, jsPsych will now automatically clear the display and clear any timeouts that are still pending. This change should only affect plugin developers. If you are using built-in plugins you should not notice any difference. From 0fc1bf0e6c6fe61a0e4c5da1bc9d378498721915 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:13:06 -0500 Subject: [PATCH 04/25] more typo fixes --- docs/plugins/initialize-camera.md | 2 +- docs/plugins/initialize-microphone.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/initialize-camera.md b/docs/plugins/initialize-camera.md index 3c07f0d011..9123253fc9 100644 --- a/docs/plugins/initialize-camera.md +++ b/docs/plugins/initialize-camera.md @@ -18,7 +18,7 @@ In addition to the [parameters available in all plugins](../overview/plugins.md# Parameter | Type | Default Value | Description ----------|------|---------------|------------ device_select_message | html string | `

Please select the camera you would like to use.

` | The message to display when the user is presented with a dropdown list of available devices. -button_label | sting | 'Use this camera.' | The label for the select button. +button_label | string | 'Use this camera.' | The label for the select button. include_audio | bool | false | Set to `true` to include an audio track in the recordings. width | int | null | Request a specific width for the recording. This is not a guarantee that this width will be used, as it depends on the capabilities of the participant's device. Learn more about `MediaRecorder` constraints [here](https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints#requesting_a_specific_value_for_a_setting). height | int | null | Request a specific height for the recording. This is not a guarantee that this height will be used, as it depends on the capabilities of the participant's device. Learn more about `MediaRecorder` constraints [here](https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints#requesting_a_specific_value_for_a_setting). diff --git a/docs/plugins/initialize-microphone.md b/docs/plugins/initialize-microphone.md index d3fabd0d62..a3e71ffc05 100644 --- a/docs/plugins/initialize-microphone.md +++ b/docs/plugins/initialize-microphone.md @@ -18,7 +18,7 @@ In addition to the [parameters available in all plugins](../overview/plugins.md# Parameter | Type | Default Value | Description ----------|------|---------------|------------ device_select_message | html string | `

Please select the microphone you would like to use.

` | The message to display when the user is presented with a dropdown list of available devices. -button_label | sting | 'Use this microphone.' | The label for the select button. +button_label | string | 'Use this microphone.' | The label for the select button. ## Data Generated From 36f75cc46a1a068335e80f3c918fc73e4091ea1f Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:12:54 -0500 Subject: [PATCH 05/25] add parametertype and build environment docs --- docs/developers/plugin-development.md | 104 ++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/docs/developers/plugin-development.md b/docs/developers/plugin-development.md index a6487e40d9..1eae9ae62f 100644 --- a/docs/developers/plugin-development.md +++ b/docs/developers/plugin-development.md @@ -61,6 +61,10 @@ const info = { } ``` +??? info "Custom Build Environments" + + If you are using a custom build environment that imports its own `tsconfig.json` file that does not extend jsPsych's, but you want to use this syntax, you must add `"resolveJsonModule": true` to the config's `compilerOptions` object. + If you are not using a build environment that supports `import` and `package.json` (such as writing a plain JS file), you can manually enter the `version` as a string. ```javascript @@ -163,6 +167,106 @@ class MyAwesomePlugin { MyAwesomePlugin.info = info; ``` +#### Parameter Types + +jsPsych currently has support for the following parameters: + +| Type Name | Description | Example | +| --------- | ----------- | ------- | +| BOOL | A simple truth value. | `true` or `false` | +| STRING | A set of characters. | "Continue" | +| INT | A value that supports whole numbers. | 12 | +| FLOAT | A value that supports decimal numbers. | 5.55 | +| FUNCTION | A Javascript function, tends to process multiple objects in an array from other parameters. | `function(tries) { return "

You have " + tries + " tries left." }` | +| KEY | A single key, with support for function keys like arrows and spacebars. | `"j"`, `"n"`, `"ArrowLeft"` | +| KEYS | Either an array of keys, or the string `"ALL_KEYS"` or `"NO_KEYS"`, indicating their respective inclusion/exclusion criterea. | `["f", "j"]` | +| SELECT | A list of strings that a developer can choose between as a parameter. | `["cm", "px", "em"]` | +| HTML_STRING | A string with HTML markup. | `"

This is the prompt.

"` | +| IMAGE | A string that contains the path to an image file. | `"my_image.jpg"` | +| AUDIO | A string that contains the path to an audio file. | `"my_sound.mp3"` | +| VIDEO | A string that contains the path to a video file. | `"my_video.mp4"` | +| OBJECT | A general JSON object (key-value pairs). | `{ rt: 350, response: "hello!", correct: true }` | +| COMPLEX | A JSON object that one can specify nested parameters for. | `{ rt: 350, response: "hello!", correct: true }` | +| TIMELINE | A jsPsych timeline object with trials. | `[{ type: jsPsychKeyboardResponse, stimulus: 'my_image.jpg }]` | + +Within each parameter, you may also specify if it is an array of the specific type. For example, a parameter that requires a list of button labels would be described as: + +```js +const info = { + // ... + parameters: { + /** The labels to be displayed on each button. */ + labels: { + type: ParameterType.STRING, + array: true, + default: ["Pause", "Play", "Continue"] + } + }, + // ... +} +``` + +Specific parameter types also have their own special markup. For `ParameterType.SELECT`, you specify the options one can choose with an `options` field, and then the `default` field must be within that field. + +```js +const info = { + // ... + parameters: { + /** The units of measure used to display the length and width of the stimulus. */ + units: { + type: ParameterType.SELECT, + options: ["em", "px", "vh", "vw"], + default: "px" + } + }, + // ... +} +``` + +For `ParameterType.COMPLEX`, we may specify the underlying fields in the object with the `nested` field. This acts in the same way as us defining parameters regularly, only we are now just delineating the fields within the object itself. + +```js +const info = { + // ... + parameters: { + /** Where to display the location of the stimuli. */ + locations: { + type: ParameterType.COMPLEX, + array: true, + default: undefined, + nested: { + /** The x-coordinate of the stimulus, in the units from the `units` field. */ + x: { + type: ParameterType.INT + }, + /** The y-coordinate of the stimulus. */ + y: { + type: ParameterType.INT + } + } + } + }, + // ... +} +``` + +For more complicated scenarios, typically when handling data generated from an arbitrary function or user input, where we have a general idea of what data type it could produce, we may also specify multiple types of data. As an example, if we know we'll get either some number (integer or float) or a string from a field, we can specify it as such: + +```js +const info = { + // ... + data: { + /** The response given by the user. */ + response: { + type: + ParameterType.INT | + ParameterType.FLOAT | + ParameterType.STRING + } + } +} +``` + ## Plugin functionality Inside the `.trial()` method you can do pretty much anything that you want, including modifying the DOM, setting up event listeners, and making asynchronous requests. In this section we'll highlight a few common things that you might want to do as examples. From 96c70920425fef3b06c4bfa5873a496dbc108b1d Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:29:05 -0500 Subject: [PATCH 06/25] fix typo --- docs/support/migration-v8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/support/migration-v8.md b/docs/support/migration-v8.md index b5cd6071b4..05911fd21f 100644 --- a/docs/support/migration-v8.md +++ b/docs/support/migration-v8.md @@ -113,7 +113,7 @@ In version 9.x, we plan to make this a requirement. In version 7.x, jsPsych's `pluginAPI` class exposed WebAudio and HTML5 audio APIs through `getAudioBuffer()`. However, this required different implementations done by the developer to account for each API. In version 8.x, we've removed this in favor of `getAudioPlayer()`, which handles both API choices under the hood. -This change only effects plugin developers. If you want to update to use the new `getAudioPlayer()`, it is recommend that you call this new method using the `await` syntax, which requires an asyncrhonous `trial` function: +This change only effects plugin developers. If you want to update to use the new `getAudioPlayer()`, it is recommend that you call this new method using the `await` syntax, which requires an asynchronous `trial` function: ```js const audio = await jsPsych.pluginAPI.getAudioPlayer('my-sound.mp3'); ``` From 8d40c445b49ea4df6d305da5a4ca00cf0b1e8ced Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:49:18 -0500 Subject: [PATCH 07/25] flesh out extension description and fix minor details --- docs/developers/extension-development.md | 10 ++++++---- docs/overview/extensions.md | 12 +++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/docs/developers/extension-development.md b/docs/developers/extension-development.md index cdfec7245f..1407c77983 100644 --- a/docs/developers/extension-development.md +++ b/docs/developers/extension-development.md @@ -66,7 +66,7 @@ let trial = { extensions: [ {type: myAwesomeExtension, params: {demo: 'value'}} ] -}); +}; //... extension code ...// class MyAwesomeExtension { @@ -91,7 +91,7 @@ let trial = { extensions: [ {type: myAwesomeExtension, params: {demo: 'value'}} ] -}); +}; //... extension code ...// class MyAwesomeExtension { @@ -122,7 +122,7 @@ let trial = { on_finish: (data) => { console.log(data.awesome); // will output 'value'. } -}); +}; //... extension code ...// class MyAwesomeExtension { @@ -168,9 +168,11 @@ The `version` field describes the version of the extension used and then durin t The `data` field is an object containing all of the `data` generated for the plugin. Each 'data' object has a `type` and `default` property. Additionally, this should be only used for data you choose to generate. Any jsdoc (comments included in the /** */ tags) you include will be scraped as metadata if you are choosing to generate metadata. This scraped metadata will also be used to create the JsPsych documentation. +For more information on the various types of parameters one can include in their data field, see [here](./plugin-development.md#parameter-types). + ### Optional methods -The extension can also include any additional methods that are necessary for interacting with it. See the [webgazer extension](../extensions/webgazer.md) for an example. +The extension can also include any additional methods that are necessary for interacting with it. See the [WebGazer extension](../extensions/webgazer.md) for an example. ## Advice for writing extensions diff --git a/docs/overview/extensions.md b/docs/overview/extensions.md index bf69628fbc..dd4a0972c0 100644 --- a/docs/overview/extensions.md +++ b/docs/overview/extensions.md @@ -1,6 +1,6 @@ # Extensions -Extensions are jsPsych modules that can interface with any plugin to extend the functionality of the plugin. A canonical example of an extension is eye tracking. An eye tracking extension allows a plugin to gather gaze data and add it to the plugin's data object. +In jsPsych, extensions allow one to extend the functionality of various plugins, giving individual plugins the ability to collect more data, display additional stimuli, and more. A canonical example of an extension is [eye tracking](../extensions/webgazer.md), which allow plugins to gather gaze data and add it to the their respective data objects. For a full list of extensions directly included in the jsPsych release, see [here](../extensions/list-of-extensions.md). ## Using an Extension @@ -16,7 +16,7 @@ To use an extension in an experiment, you'll load the extension file via a ` Date: Sat, 16 Nov 2024 09:54:08 -0500 Subject: [PATCH 08/25] remake video into community tutorials, add examples --- docs/tutorials/community-tutorials.md | 45 +++++++++++++++++++++++++++ docs/tutorials/video-tutorials.md | 14 --------- mkdocs.yml | 2 +- 3 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 docs/tutorials/community-tutorials.md delete mode 100644 docs/tutorials/video-tutorials.md diff --git a/docs/tutorials/community-tutorials.md b/docs/tutorials/community-tutorials.md new file mode 100644 index 0000000000..d3063f5e10 --- /dev/null +++ b/docs/tutorials/community-tutorials.md @@ -0,0 +1,45 @@ +# Community Tutorials + +If you're interested in learning more about jsPsych, prefer video tutorials, or would like to just see other perspectives on how to use the codebase, we have some community resources that can assist you. If you have an idea for a tutorial to add, if you want to make your own, or if you have found a particularly helpful resource while online, be sure to tell us through our [issues board](https://github.com/jspsych/jsPsych/issues) so we may potentially feature it here! + +## Version 8.x + +Currently there many aren't any tutorials specific to jsPsych version `8.x` due to the relatively recent release date. However, concerning most experiments, the `7.x` tutorials should provide plenty of information. Along with this, our [migration guide](../support/migration-v8.md) can also assist in bridging the gap, though most breaking changes have to do with plugin development, rather than experiment building. + +### University of Edinburgh + +This [tutorial](https://softdev.ppls.ed.ac.uk/online_experiments/index.html) provided by the folks at the University of Edinburgh is primarily used for students learning how to construct online experiments with jsPsych, but there is plenty of helpful information on here for anyone. A [basic tutorial](https://softdev.ppls.ed.ac.uk/online_experiments/02_first.html) is initially provided, along with framework-specific topics like timeline variables and factorial design [covered here](https://softdev.ppls.ed.ac.uk/online_experiments/04_jspsych.html). There are some more advanced tutorials covering [participant and condition numbers](https://softdev.ppls.ed.ac.uk/online_experiments/07_ppt.html), among other tutorials that should give you a deeper understanding of JavaScript and our framework. + +## Version 7.x + +These tutorials use version `7.x` of jsPsych. + +### jamesbrandscience + +This very handy [website](https://jamesbrandscience.github.io/tutorials/) focuses primarily on teaching how to use [Cognition.run](https://www.cognition.run/), a hosting service for jsPsych experiments, in tandem with teaching jsPsych. It is split into an [introduction](https://jamesbrandscience.github.io/jspsych-tutorial/jspsych_1.html) with jsPsych and Cognition.run, the basics of [customizing timelines, CSS, and data processing](https://jamesbrandscience.github.io/jspsych-tutorial/jspsych_2.html), an overview of a [proper experiment](https://jamesbrandscience.github.io/jspsych-tutorial/jspsych_2.html) with the `fullscreen`, `instructions`, and `survey-html-form` plugins, and finally, a tutorial on using [advanced stimuli](https://jamesbrandscience.github.io/jspsych-tutorial/jspsych_4.html), such as audio and images. For those interested in contributing to jsPsych, but have limited knowledge of Git and GitHub, there is also a very helpful [tutorial](https://jamesbrandscience.github.io/tutorials/GitHub_stats_chat/GitHub.html) for that as well. + +### Cognition and Learning Lab @ Purdue University + +Purdue University's Cognition and Learning Lab has a [simple tutorial](https://learninglab.psych.purdue.edu/handbook/programming-guide/getting-started-jspsych-7/) on utilizing jsPsych `7.x`. The rest of the tutorials pertaining to jsPsych on this website are written in jsPsych `6.x`, but some of the information may still be applicable assuming you have a good grasp on how `7.x` works. + +### Crump Lab @ Brooklyn College + +The Psyc 2001 course at Brooklyn College used a combination of [Quarto](https://quarto.org/), an academic blogging workflow in RStudio, along with jsPsych to create tutorials on setting up a [Quarto blog](https://www.crumplab.com/blog/post_887_8_25_22_quartoblog/). While most of the advanced posts on the [course blog](https://www.crumplab.com/psyc2001/blog.html) concern data processing and R, the first few tutorials have videos accompanied for a basic overview on [web development](https://www.crumplab.com/psyc2001/blog/2_Basic_web/), our simple [reaction time tutorial](https://www.crumplab.com/psyc2001/blog/3_jspsych_tutorial/), and programming a [Stroop experiment](https://www.crumplab.com/psyc2001/blog/3_jspsych_tutorial/) in jsPsych. + +### (中文) Resources in Chinese + +Contributor [Shaobin-Jiang](https://github.com/Shaobin-Jiang) has created resources specifically tailored to those who require a Chinese language tutorial, including a full [video tutorial](https://www.bilibili.com/video/BV1Qs4y1y7c9/) and a version of this [documentation](https://shaobin-jiang.github.io/jsPsych-Chinese-Documentation/), in Chinese. + +## Version 6.x + +These tutorials use version `6.x` of jsPsych, and are not very applicable to the modern patterns of jsPsych. If you are to use this for a starting point, make sure to read the tutorials on this website to understand how `8.x` works, watching these videos for any additional information. Some of the content still applies to `8.x` and their corresponding plugins, but a solid understanding of the current framework is required in order to make use of the examples in the videos. + +### YouTube Channel + +A variety of video tutorials are available on [Josh de Leeuw's YouTube channel](https://www.youtube.com/playlist?list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh). Some tutorials walk through creating a basic version of an entire experiment, like the tutorial on creating a [dichotic listening experiment](https://www.youtube.com/playlist?list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh) aimed at new users. Others focus on specific features of jsPsych, like how to use [functions as parameters](https://www.youtube.com/watch?v=8-j2aAZ_iOk&list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh&index=5) to create experiments that change in response to participant input or how to [create a new plugin](https://www.youtube.com/watch?v=XQcsFwAmbiw&list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh&index=4). + +### Workshops + +**Moving Research Online (2020)**. Recordings from a [Summer 2020 workshop](https://www.movingresearchonline.info) on conducting online research are available on the [workshop's YouTube channel](https://www.youtube.com/channel/UCBZ5F1UysHWlplUNDRwbsWA). [Session 1](https://www.youtube.com/watch?v=BuhfsIFRFe8) provides an overview of jsPsych suitable for brand new users. [Session 3](https://www.youtube.com/watch?v=LP7o0iAALik) covers some more advanced features of jsPsych. This workshop was funded by the National Science Foundation. + +**babySTEP (2021)**. The Centre for Comparative Psycholinguistics (CCP, University of Alberta Department of Linguistics) hosted a two-part jsPsych workshop in 2021 as part of their annual [STEP program](https://ccp.artsrn.ualberta.ca/portfolio/step/). [Day 1](https://drive.google.com/file/d/1_bd_Tz1IoyGaZzuPoR_Qb6Rtd5wg4t4D/view?usp=drive_web) covered the basics of creating a jsPsych experiment, with an emphasis on audio stimuli. [Day 2](https://drive.google.com/file/d/1dIw1xIVY1lCHwFKGRaUnWMguwHfdkbGK/view?usp=drive_web) was organized around pre-submitted questions. The video demonstrates how to create a more complex experiment involving reading a sentence and hearing different audio options for completing the sentences, and answers several questions about timing accuracy, recording participant generated audio, embedding jsPsych into course (or other) websites, and some (non-empirical) advice about attention checks. \ No newline at end of file diff --git a/docs/tutorials/video-tutorials.md b/docs/tutorials/video-tutorials.md deleted file mode 100644 index efc58a387a..0000000000 --- a/docs/tutorials/video-tutorials.md +++ /dev/null @@ -1,14 +0,0 @@ -# Video tutorials - -!!! warning - Most of these videos are using version `6.x` of jsPsych. Using version `7.x` requires a few changes that are not covered in these videos. We recommend starting with the tutorials on this website to understand how to work with `7.x` and then watching these videos for additional information. Much of the content covered in the videos still applies to `7.x`, but you'll need a solid understanding of how to use `7.x` in order to make use of the examples in the videos. - -## YouTube Channel - -A variety of video tutorials are available on [Josh de Leeuw's YouTube channel](https://www.youtube.com/playlist?list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh). Some tutorials walk through creating a basic version of an entire experiment, like the tutorial on creating a [dichotic listening experiment](https://www.youtube.com/playlist?list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh) aimed at new users. Others focus on specific features of jsPsych, like how to use [functions as parameters](https://www.youtube.com/watch?v=8-j2aAZ_iOk&list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh&index=5) to create experiments that change in response to participant input or how to [create a new plugin](https://www.youtube.com/watch?v=XQcsFwAmbiw&list=PLnfo1lBY1P2Mf_o6rV5wiqqn92Mw3UTGh&index=4). - -## Workshops - -**Moving Research Online (2020)**. Recordings from a [Summer 2020 workshop](https://www.movingresearchonline.info) on conducting online research are available on the [workshop's YouTube channel](https://www.youtube.com/channel/UCBZ5F1UysHWlplUNDRwbsWA). [Session 1](https://www.youtube.com/watch?v=BuhfsIFRFe8) provides an overview of jsPsych suitable for brand new users. [Session 3](https://www.youtube.com/watch?v=LP7o0iAALik) covers some more advanced features of jsPsych. This workshop was funded by the National Science Foundation. - -**babySTEP (2021)**. The Centre for Comparative Psycholinguistics (CCP, University of Alberta Department of Linguistics) hosted a two-part jsPsych workshop in 2021 as part of their annual [STEP program](https://ccp.artsrn.ualberta.ca/portfolio/step/). [Day 1](https://drive.google.com/file/d/1_bd_Tz1IoyGaZzuPoR_Qb6Rtd5wg4t4D/view?usp=drive_web) covered the basics of creating a jsPsych experiment, with an emphasis on audio stimuli. [Day 2](https://drive.google.com/file/d/1dIw1xIVY1lCHwFKGRaUnWMguwHfdkbGK/view?usp=drive_web) was organized around pre-submitted questions. The video demonstrates how to create a more complex experiment involving reading a sentence and hearing different audio options for completing the sentences, and answers several questions about timing accuracy, recording participant generated audio, embedding jsPsych into course (or other) websites, and some (non-empirical) advice about attention checks. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index e5495598c7..87300d1208 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,7 +43,7 @@ nav: - Tutorials: - 'The Basics: Hello World': 'tutorials/hello-world.md' - 'Demo Experiment: Simple Reaction Time Task': 'tutorials/rt-task.md' - - 'Video Tutorials': 'tutorials/video-tutorials.md' + - 'Community Tutorials': 'tutorials/community-tutorials.md' - Overview: - 'Creating an Experiment: The Timeline': 'overview/timeline.md' - 'Plugins': 'overview/plugins.md' From 8bbd69266b77adae04bee9c36b092e0e782c8fa0 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:30:32 -0500 Subject: [PATCH 09/25] add missing documentation in audio-slider-response --- docs/plugins/audio-slider-response.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plugins/audio-slider-response.md b/docs/plugins/audio-slider-response.md index 77eb6b9e1d..7e40a99c52 100644 --- a/docs/plugins/audio-slider-response.md +++ b/docs/plugins/audio-slider-response.md @@ -28,6 +28,7 @@ In addition to the [parameters available in all plugins](../overview/plugins.md# | prompt | string | null | This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that it can be used to provide a reminder about the action the participant is supposed to take (e.g., which key to press). | | trial_duration | numeric | null | How long to wait for the participant to make a response before ending the trial in milliseconds. If the participant fails to make a response before this timer is reached, the participant's response will be recorded as null for the trial and the trial will end. If the value of this parameter is null, then the trial will wait for a response indefinitely. | | response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. | +| trial_ends_after_audio | boolean | false | If true, then the trial will end as soon as the audio file finishes playing. | | response_allowed_while_playing | boolean | true | If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before the slider is enabled and the trial can end via the next button click. Once the audio has played all the way through, the slider is enabled and a response is allowed (including while the audio is being re-played via on-screen playback controls). | ## Data Generated From e5836b7a04832c82e6f1c6886ba3ad9743361af3 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:52:41 -0500 Subject: [PATCH 10/25] fix warning message --- docs/overview/media-preloading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/overview/media-preloading.md b/docs/overview/media-preloading.md index 0a7573fc51..6a27850fda 100644 --- a/docs/overview/media-preloading.md +++ b/docs/overview/media-preloading.md @@ -3,7 +3,7 @@ If an experiment uses image, audio, or video files as stimuli, it is a good idea to preload the files before running the experiment. You can preload files at any point in your experiment using the [jsPsych `preload` plugin](../plugins/preload.md). Preloading files means that the participant's browser will download the files and store them in local memory on the participant's computer. This is important because displaying or playing a media file is much faster if it is already in memory on the participant's computer. Without preloading, there will be noticeable delays in the display of media, which will affect any timing measurements (such as how long an image is displayed, or a participant's response time since first viewing an image). For particularly large files, like video, preloading content avoids lengthy pauses in the middle of the experiment that can be disruptive to the flow of the experiment. !!! warning - Note that video preloading will not work when you run your experiment offline (e.g., by double-clicking on the HTML file), but it will work once your experiment is running online (hosted on a server). The [Cross-origin requests (CORS) and safe mode](running-experiments.md#cross-origin-requests-cors-and-safe-mode) section on the Running Experiments page contains more information about this. + Note that video preloading will not work when you run your experiment offline (e.g., by double-clicking on the HTML file), but it will work once your experiment is running online (hosted on a server). The [Cross-origin requests (CORS) and safe mode](running-experiments.md#cross-origin-requests-cors-and-safe-mode) section on the Running Experiments page contains more information about this. ## Automatic Preloading From 6470d4092c7646eb26e971443d6c3a3a2c94072b Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Wed, 20 Nov 2024 01:01:56 -0500 Subject: [PATCH 11/25] delete functions removed in v8 --- docs/reference/jspsych-pluginAPI.md | 63 +---------------- docs/reference/jspsych.md | 105 +++------------------------- 2 files changed, 11 insertions(+), 157 deletions(-) diff --git a/docs/reference/jspsych-pluginAPI.md b/docs/reference/jspsych-pluginAPI.md index ffc7da3b58..f467cb6206 100644 --- a/docs/reference/jspsych-pluginAPI.md +++ b/docs/reference/jspsych-pluginAPI.md @@ -234,7 +234,7 @@ Returns a Promise that resolves to an instance of an AudioPlayer class that hold #### Description -Gets an AudioPlayer class instance which has methods that can be used to play or stop audio that can be played with the WebAudio API or an audio object that can be played as HTML5 Audio. +Gets an AudioPlayer class instance which has methods that can be used to play or stop audio that can be played with the WebAudio API or an audio object that can be played as HTML5 Audio. By default, jsPsych uses the WebAudio API to play audio in the browser, so if you want to use HTML5 Audio, be sure to put `use_webaudio: false` in the `initJsPsych` object. It is strongly recommended that you preload audio files before calling this method. This method will load the files if they are not preloaded, but this may result in delays during the experiment as audio is downloaded. @@ -384,63 +384,6 @@ See the `audio-keyboard-response` plugin for an example in a fuller context. ## Other Media -### getAudioBuffer - -```javascript -jsPsych.pluginAPI.getAudioBuffer(filepath) -``` - -#### Parameters - -Parameter | Type | Description -----------|------|------------ -filepath | string | The path to the audio file that was preloaded. - -#### Return value - -Returns a Promise that resolves when the audio file loads. Success handler's parameter will be the audio buffer. If the experiment is running using the WebAudio API it will be an AudioBuffer object. Otherwise, it will be an HTML5 Audio object. The failure handler's parameter is the error generated by `preloadAudio`. - -#### Description - -Gets an AudioBuffer that can be played with the WebAudio API or an Audio object that can be played with HTML5 Audio. - -It is strongly recommended that you preload audio files before calling this method. This method will load the files if they are not preloaded, but this may result in delays during the experiment as audio is downloaded. - -#### Examples - -##### HTML 5 Audio - -```javascript -jsPsych.pluginAPI.getAudioBuffer('my-sound.mp3') - .then(function(audio){ - audio.play(); - }) - .catch(function(err){ - console.error('Audio file failed to load') - }) -``` - -##### WebAudio API - -```javascript -var context = jsPsych.pluginAPI.audioContext(); - -jsPsych.pluginAPI.getAudioBuffer('my-sound.mp3') - .then(function(buffer){ - audio = context.createBufferSource(); - audio.buffer = buffer; - audio.connect(context.destination); - audio.start(context.currentTime); - }) - .catch(function(err){ - console.error('Audio file failed to load') - }) -``` - -See the `audio-keyboard-response` plugin for an example in a fuller context. - ---- - ### getAutoPreloadList ```javascript @@ -564,7 +507,7 @@ None. #### Description -Generates a `MediaRecorder` object from provided `MediaStream` and stores this for access via [getCameraRecorder()](#getcamerarecorder). +Generates a `MediaRecorder` object from provided `MediaStream` and stores this for access via [`getCameraRecorder()`](#getcamerarecorder). #### Example @@ -597,7 +540,7 @@ None. #### Description -Generates a `MediaRecorder` object from provided `MediaStream` and stores this for access via [getMicrophoneRecorder()](#getmicrophonerecorder). +Generates a `MediaRecorder` object from provided `MediaStream` and stores this for access via [`getMicrophoneRecorder()`](#getmicrophonerecorder). #### Example diff --git a/docs/reference/jspsych.md b/docs/reference/jspsych.md index da29234b76..a4d4260307 100644 --- a/docs/reference/jspsych.md +++ b/docs/reference/jspsych.md @@ -228,42 +228,6 @@ const memoryTestProcedure = { ``` ---- -## jsPsych.addNodeToEndOfTimeline - -```javascript -jsPsych.addNodeToEndOfTimeline(node_parameters) -``` - -### Parameters - -| Parameter | Type | Description | -| --------------- | -------- | ---------------------------------------- | -| node_parameters | object | An object defining a timeline. It must have, at a minimum, a `timeline` parameter with a valid timeline array as the value for that parameter. | - -### Return value - -None. - -### Description - -Adds the timeline to the end of the experiment. - -### Example - -```javascript -var trial = { - type: jsPsychHtmlKeyboardResponse, - stimulus: 'This is a new trial.' -} - -var new_timeline = { - timeline: [trial] -} - -jsPsych.addNodeToEndOfTimeline(new_timeline) -``` - --- ## jsPsych.evaluateTimelineVariable @@ -330,13 +294,14 @@ Returns nothing. ### Description -This method tells jsPsych that the current trial is over. It is used in all of the plugins to end the current trial. When the trial ends a few things happen: +This method tells jsPsych that the current trial is over. It is used in all of the plugins to end the current trial. When the trial ends, a few things happen: * The data is stored using `jsPsych.data.write()` -* The on_finish callback function is executed for the trial -* The on_trial_finish callback function is executed +* The `on_finish` callback function is executed for the trial +* The `on_trial_finish` callback function is executed +* The display element is cleared, and any timeouts that are pending are cleared. * The progress bar is updated if it is being displayed -* The experiment ends if the trial is the last one (and the on_finish callback function is executed). +* The experiment ends if the trial is the last one (and the `on_finish` callback function is executed). * The next trial, if one exists, is started. ### Example @@ -363,7 +328,7 @@ Returns the object describing the current trial. The object will contain all of ### Description -Get a description of the current trial +Get a description of the current trial. ### Example @@ -467,31 +432,6 @@ alert('You have completed approximately '+progress.percent_complete+'% of the ex ``` ---- -## jsPsych.getProgressBarCompleted - -```javascript -jsPsych.getProgressBarCompleted() -``` - -### Parameters - -None. - -### Return value - -Returns a value between 0 and 1 representing how full the progress bar currently is. - -### Description - -Used to get the current value of the progress bar. Works for automated and manual control. - -### Example - -```javascript -var progress_bar_amount = jsPsych.getProgressBarCompleted(); -``` - --- ## jsPsych.getStartTime @@ -570,9 +510,9 @@ Pauses the experiment. The experiment will finish the current trial, but will no var trial = { type: jsPsychHtmlKeyboardResponse, stimulus: 'Press p to take a 30 second break. Otherwise, press c to continue immediately.', - choices: ['p','c'], + choices: ['p', 'c'], on_finish: function(data){ - if(jsPsych.pluginAPI.compareKeys(data.response, "p")) { + if (jsPsych.pluginAPI.compareKeys(data.response, "p")) { jsPsych.pauseExperiment(); setTimeout(jsPsych.resumeExperiment, 30000); } @@ -648,35 +588,6 @@ jsPsych.run(timeline); --- -## jsPsych.setProgressBar - -```javascript -jsPsych.setProgressBar(value) -``` - -### Parameters - -| Parameter | Type | Description | -| --------- | ------- | ---------------------------------------- | -| value | numeric | Proprotion (between 0 and 1) to fill the progress bar. | - - -### Return value - -None. - -### Description - -Set the progress bar to a custom amount. Proportion must be between 0 and 1. Values larger than 1 are treated as 1. - -### Example - -```javascript -jsPsych.setProgressBar(0.85); -``` - ---- - ## jsPsych.timelineVariable ```javascript From 2f859b88871b9ecd8210bdf5b828ab5b1f9934ce Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Wed, 20 Nov 2024 01:51:41 -0500 Subject: [PATCH 12/25] fix to use evaluateTimelineVariable --- docs/overview/building-surveys.md | 4 ++-- examples/extension-record-video.html | 2 +- examples/webgazer.html | 4 ++-- .../examples/dynamically_generating_content.html | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/overview/building-surveys.md b/docs/overview/building-surveys.md index 257a695410..5a5dccfb38 100644 --- a/docs/overview/building-surveys.md +++ b/docs/overview/building-surveys.md @@ -387,7 +387,7 @@ Rather than repeating a question format within the same trial, perhaps you want elements: [ { type: "text", - title: `Enter a word related to ${jsPsych.timelineVariable('word').toUpperCase()}:`, + title: `Enter a word related to ${jsPsych.evaluteTimelineVariable('word').toUpperCase()}:`, autocomplete: "off" } ], @@ -416,7 +416,7 @@ Rather than repeating a question format within the same trial, perhaps you want // Create question using timeline variables const page = survey.addNewPage('page1'); const question = page.addNewQuestion('text'); - question.title = `Enter a word related to ${jsPsych.timelineVariable('word').toUpperCase()}`; + question.title = `Enter a word related to ${jsPsych.evaluateTimelineVariable('word').toUpperCase()}`; question.autocomplete = "off"; // Set survey-level parameters survey.showQuestionNumbers = false; diff --git a/examples/extension-record-video.html b/examples/extension-record-video.html index 10108a75c5..29cf9ee570 100644 --- a/examples/extension-record-video.html +++ b/examples/extension-record-video.html @@ -32,7 +32,7 @@ type: jsPsychHtmlKeyboardResponse, stimulus: ()=>{ let html = `
-
+
`; return html; }, diff --git a/examples/webgazer.html b/examples/webgazer.html index c7d19455d5..a1a04cfffa 100644 --- a/examples/webgazer.html +++ b/examples/webgazer.html @@ -109,8 +109,8 @@ stimulus: function () { return( `
-
- ${jsPsych.timelineVariable('direction', true) == 'left' ? '⬅' : '➡'} +
+ ${jsPsych.evaluateTimelineVariable('direction', true) == 'left' ? '⬅' : '➡'}
` ) diff --git a/packages/plugin-survey/examples/dynamically_generating_content.html b/packages/plugin-survey/examples/dynamically_generating_content.html index 15c3df1b4e..be3f4ea396 100644 --- a/packages/plugin-survey/examples/dynamically_generating_content.html +++ b/packages/plugin-survey/examples/dynamically_generating_content.html @@ -100,7 +100,7 @@ type: jsPsychHtmlKeyboardResponse, stimulus: function () { var html = ` -
${jsPsych.timelineVariable('word1')}${jsPsych.timelineVariable('word2')}
`; +
${jsPsych.evaluateTimelineVariable('word1')}${jsPsych.evaluateTimelineVariable('word2')}
`; return html; }, choices: ['f', 'j'], @@ -112,7 +112,7 @@ survey_json: function () { const last_response = jsPsych.data.getLastTrialData().values()[0].response; const response_type = (last_response === 'j') ? "RELATED" : "NOT RELATED"; - const question_text = `You said that the words "${jsPsych.timelineVariable('word1')}" and "${jsPsych.timelineVariable('word2')}" are ${response_type}. Please explain your answer.`; + const question_text = `You said that the words "${jsPsych.evaluateTimelineVariable('word1')}" and "${jsPsych.evaluateTimelineVariable('word2')}" are ${response_type}. Please explain your answer.`; const survey_json = { showQuestionNumbers: false, completeText: "Next", From f4ba84a3a78d0a9a14edd3b8f91a81609d7fa129 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:12:44 -0500 Subject: [PATCH 13/25] fix wording and some links --- docs/developers/extension-development.md | 2 +- docs/developers/plugin-development.md | 6 +++--- docs/reference/jspsych-pluginAPI.md | 2 +- docs/support/migration-v8.md | 7 ++----- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/developers/extension-development.md b/docs/developers/extension-development.md index 1407c77983..f7c4c800b1 100644 --- a/docs/developers/extension-development.md +++ b/docs/developers/extension-development.md @@ -168,7 +168,7 @@ The `version` field describes the version of the extension used and then durin t The `data` field is an object containing all of the `data` generated for the plugin. Each 'data' object has a `type` and `default` property. Additionally, this should be only used for data you choose to generate. Any jsdoc (comments included in the /** */ tags) you include will be scraped as metadata if you are choosing to generate metadata. This scraped metadata will also be used to create the JsPsych documentation. -For more information on the various types of parameters one can include in their data field, see [here](./plugin-development.md#parameter-types). +For more information on the various types of parameters one can include in their data field, see our [documentation on `ParameterType`s](./plugin-development.md#parameter-types). ### Optional methods diff --git a/docs/developers/plugin-development.md b/docs/developers/plugin-development.md index 1eae9ae62f..caaa9745f7 100644 --- a/docs/developers/plugin-development.md +++ b/docs/developers/plugin-development.md @@ -61,9 +61,9 @@ const info = { } ``` -??? info "Custom Build Environments" +??? info "Automatic versioning with custom build environments" - If you are using a custom build environment that imports its own `tsconfig.json` file that does not extend jsPsych's, but you want to use this syntax, you must add `"resolveJsonModule": true` to the config's `compilerOptions` object. + If you are using a custom build environment that imports its own `tsconfig.json` file that does not extend jsPsych's, and you want to use this automatic versioning syntax, you must add `"resolveJsonModule": true` to the config's `compilerOptions` object. If you are not using a build environment that supports `import` and `package.json` (such as writing a plain JS file), you can manually enter the `version` as a string. @@ -169,7 +169,7 @@ MyAwesomePlugin.info = info; #### Parameter Types -jsPsych currently has support for the following parameters: +jsPsych currently has support for the following parameter types: | Type Name | Description | Example | | --------- | ----------- | ------- | diff --git a/docs/reference/jspsych-pluginAPI.md b/docs/reference/jspsych-pluginAPI.md index f467cb6206..b7fa6005eb 100644 --- a/docs/reference/jspsych-pluginAPI.md +++ b/docs/reference/jspsych-pluginAPI.md @@ -298,7 +298,7 @@ Returns nothing. #### Description -Method that belongs to the AudioPlayer class. Stops the audio loaded into the audio buffer of the AudioPlayer instance for a particular file. If the audio is an HTML5 audio object it pauses it. If the audio is a Webaudio API object it stops it. This will regenerate the audio player, allowing you to call the `play()` method upon it again. +Method that belongs to the AudioPlayer class. Stops the audio loaded into the audio buffer of the AudioPlayer instance for a particular file. If the audio is an HTML5 audio object it pauses it. If the audio is a WebAudio API object it stops it. This will regenerate the audio player, allowing you to call the `play()` method upon it again. #### Example diff --git a/docs/support/migration-v8.md b/docs/support/migration-v8.md index 05911fd21f..7c107ba86a 100644 --- a/docs/support/migration-v8.md +++ b/docs/support/migration-v8.md @@ -86,9 +86,6 @@ const trial = { The `button_html` parameter can also support different HTML for each button. See the [plugin documentation](https://www.jspsych.org/latest/plugins/html-button-response/index.html) for more details. -For plugin developers: if you are writing a plugin and updating parameters to use functions, -make sure to mock these functions in Jest to ensure tests can still run. - ## Plugin parameter handling In version 7.x, a plugin could omit parameters from the `info` object and jsPsych would still evaluate these parameters appropriately in most cases. @@ -113,7 +110,7 @@ In version 9.x, we plan to make this a requirement. In version 7.x, jsPsych's `pluginAPI` class exposed WebAudio and HTML5 audio APIs through `getAudioBuffer()`. However, this required different implementations done by the developer to account for each API. In version 8.x, we've removed this in favor of `getAudioPlayer()`, which handles both API choices under the hood. -This change only effects plugin developers. If you want to update to use the new `getAudioPlayer()`, it is recommend that you call this new method using the `await` syntax, which requires an asynchronous `trial` function: +This change only affects plugin developers. If you want to update to use the new `getAudioPlayer()`, it is recommend that you call this new method using the `await` syntax, which requires an asynchronous `trial` function: ```js const audio = await jsPsych.pluginAPI.getAudioPlayer('my-sound.mp3'); ``` @@ -148,7 +145,7 @@ Along with this, the `start()` and `pause()` functions were removed from the `Au You can still call `stop()` upon an audio ending in order to regenerate the `AudioPlayer`, and be able to call `play()` on it again. -For a general guide on implementation, the `audio-button-response` plugin uses the `await` syntax +For a general guide on implementation, the [`audio-button-response`](https://github.com/jspsych/jsPsych/blob/main/packages/plugin-audio-button-response/src/index.ts) plugin uses the `await` syntax to handle playing audio. ## Changes to `finishTrial()` From cd7821d63ca6c1fcfb8010dbe992668b83084d6f Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:10:44 -0500 Subject: [PATCH 14/25] fix readme link, update contributors --- README.md | 2 +- contributors.md | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f67108acd3..6589c09b59 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ You can learn more about setting up a project by following the [hello world tuto Once you've got a project set up, the [reaction time task tutorial](https://www.jspsych.org/latest/tutorials/rt-task/) is a great next step, since it covers many core topics and features. -There are also a number of [video tutorials](https://www.jspsych.org/latest/tutorials/video-tutorials) available on the website. +There are also a number of [community tutorials](https://www.jspsych.org/latest/tutorials/community-tutorials) available on the website. ## Examples diff --git a/contributors.md b/contributors.md index 4bc62a9749..17acdf5fe0 100644 --- a/contributors.md +++ b/contributors.md @@ -3,11 +3,15 @@ The following people have contributed to the development of jsPsych by writing c * Antonia - https://github.com/Ahoidal * aucuparia - https://github.com/aucuparia * Xiaolu Bai - https://github.com/lbai001 +* Niranjan Baskaran - https://github.com/Bankminer78 +* brchristian - https://github.com/brchristian * bjoluc - https://github.com/bjoluc * Christian Brickhouse - https://github.com/chrisbrickhouse * Teon L Brooks - https://github.com/teonbrooks +* Susan Buck - https://github.com/susanBuck * Eamon Caddigan - https://github.com/eamoncaddigan * Jason Carpenter +* Cherrie Chang - https://github.com/cherriechang * Steve Chao - https://github.com/stchao * Zhanwen "Phil" Chen - https://github.com/zhanwenchen * cthorey - https://github.com/cthorey @@ -20,15 +24,17 @@ The following people have contributed to the development of jsPsych by writing c * Becky Gilbert - https://github.com/becky-gilbert * Mark Gorenstein - https://github.com/mgorenstein * Rui Han - https://github.com/hrcn +* Eitan Hemed - https://github.com/EitanHemed * Andy Heusser - https://github.com/andrewheusser * Angus Hughes - https://github.com/awhug -* jadeddelta - https://github.com/jadeddelta +* jade - https://github.com/jadeddelta * Gustavo Juantorena - https://github.com/GEJ1 * Chris Jungerius - https://github.com/cjungerius * George Kachergis - https://github.com/kachergis * Yul Kang - https://github.com/yulkang * Spencer King - https://github.com/spencerking * Jana Klaus - https://github.com/janakl4us +* Ethan Knights - https://github.com/ethanknights * Arnold Kochari - https://github.com/akochari * Peter Jes Kohler - https://github.com/pjkohler * kupiqu - https://github.com/kupiqu @@ -38,6 +44,7 @@ The following people have contributed to the development of jsPsych by writing c * madebyafox - https://github.com/madebyafox * Shane Martin - https://github.com/shamrt * Vijay Marupudi - https://github.com/vijaymarupudi +* Cian Monnin - https://github.com/CMonnin * Adrian Oesch - https://github.com/adrianoesch * Benjamin Ooghe-Tabanou - https://github.com/boogheta * Nikolay B Petrov - https://github.com/nikbpetrov @@ -64,4 +71,5 @@ The following people have contributed to the development of jsPsych by writing c * Reto Wyss - https://github.com/retowyss * Shaobin Jiang - https://github.com/Shaobin-Jiang * Haotian Tu - https://github.com/thtTNT -* Joshua Unrau - https://github.com/joshunrau \ No newline at end of file +* Joshua Unrau - https://github.com/joshunrau +* Victor Zhang - https://github.com/vzhang03 \ No newline at end of file From a738531091067af661936db1430c49895fae8143 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Sat, 30 Nov 2024 18:01:45 -0500 Subject: [PATCH 15/25] remove multiple parametertype documentation --- docs/developers/plugin-development.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/developers/plugin-development.md b/docs/developers/plugin-development.md index caaa9745f7..15cb26fc63 100644 --- a/docs/developers/plugin-development.md +++ b/docs/developers/plugin-development.md @@ -250,23 +250,6 @@ const info = { } ``` -For more complicated scenarios, typically when handling data generated from an arbitrary function or user input, where we have a general idea of what data type it could produce, we may also specify multiple types of data. As an example, if we know we'll get either some number (integer or float) or a string from a field, we can specify it as such: - -```js -const info = { - // ... - data: { - /** The response given by the user. */ - response: { - type: - ParameterType.INT | - ParameterType.FLOAT | - ParameterType.STRING - } - } -} -``` - ## Plugin functionality Inside the `.trial()` method you can do pretty much anything that you want, including modifying the DOM, setting up event listeners, and making asynchronous requests. In this section we'll highlight a few common things that you might want to do as examples. From ad1d854f43c1e25ba988a3aa2a23a8ab22be3535 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Fri, 10 Jan 2025 01:02:58 -0700 Subject: [PATCH 16/25] rework tests to use `displayElement`, remove DOM clearing per test --- .changeset/tender-ads-prove.md | 5 + packages/config/jest.cjs | 3 +- packages/config/jest.setup.js | 3 - .../core/functions-as-parameters.test.ts | 26 ++--- .../jspsych/tests/core/progressbar.test.ts | 37 ++++--- .../tests/data/data-csv-conversion.test.ts | 16 +-- .../tests/data/data-json-conversion.test.ts | 16 +-- .../tests/data/trialparameters.test.ts | 8 +- .../src/index.spec.ts | 4 +- packages/plugin-cloze/src/index.spec.ts | 101 +++++++++--------- packages/plugin-fullscreen/src/index.spec.ts | 8 +- .../src/index.spec.ts | 4 +- .../src/index.spec.ts | 4 +- .../src/index.spec.ts | 4 +- .../src/index.spec.ts | 11 +- .../src/index.spec.ts | 24 ++--- packages/plugin-maxdiff/src/index.spec.ts | 8 +- .../src/index.spec.ts | 21 ++-- .../src/index.spec.ts | 44 ++++---- .../plugin-survey-html-form/src/index.spec.ts | 2 +- .../plugin-survey-likert/src/index.spec.ts | 21 ++-- .../src/index.spec.ts | 30 +++--- .../src/index.spec.ts | 22 ++-- packages/plugin-survey-text/src/index.spec.ts | 40 +++---- .../src/index.spec.ts | 6 +- 25 files changed, 251 insertions(+), 217 deletions(-) create mode 100644 .changeset/tender-ads-prove.md delete mode 100644 packages/config/jest.setup.js diff --git a/.changeset/tender-ads-prove.md b/.changeset/tender-ads-prove.md new file mode 100644 index 0000000000..a0548b90c3 --- /dev/null +++ b/.changeset/tender-ads-prove.md @@ -0,0 +1,5 @@ +--- +"@jspsych/config": patch +--- + +remove DOM clearing after each individual test, fixes issues with testing in other repositories diff --git a/packages/config/jest.cjs b/packages/config/jest.cjs index 43ea363093..c4f24a3706 100644 --- a/packages/config/jest.cjs +++ b/packages/config/jest.cjs @@ -18,7 +18,6 @@ module.exports.makePackageConfig = (dirname) => { displayName: { name: packageBaseName, color: packageBaseName === "jspsych" ? "white" : "cyanBright", - }, - setupFilesAfterEnv: ["../config/jest.setup.js"], + } }; }; diff --git a/packages/config/jest.setup.js b/packages/config/jest.setup.js deleted file mode 100644 index 4c6b91b318..0000000000 --- a/packages/config/jest.setup.js +++ /dev/null @@ -1,3 +0,0 @@ -global.afterEach(() => { - document.body.innerHTML = ''; -}); \ No newline at end of file diff --git a/packages/jspsych/tests/core/functions-as-parameters.test.ts b/packages/jspsych/tests/core/functions-as-parameters.test.ts index 472866457d..b6ba66cff2 100644 --- a/packages/jspsych/tests/core/functions-as-parameters.test.ts +++ b/packages/jspsych/tests/core/functions-as-parameters.test.ts @@ -21,7 +21,7 @@ describe("standard use of function as parameter", () => { test("parameters can be protected from early evaluation using ParameterType.FUNCTION", async () => { const mock = jest.fn(); - await startTimeline([ + const { displayElement } = await startTimeline([ { type: cloze, text: "%foo%", @@ -31,7 +31,7 @@ describe("standard use of function as parameter", () => { ]); expect(mock).not.toHaveBeenCalled(); - await clickTarget(document.querySelector("#finish_cloze_button")); + await clickTarget(displayElement.querySelector("#finish_cloze_button")); expect(mock).toHaveBeenCalledTimes(1); }); }); @@ -76,12 +76,12 @@ describe("nested parameters as functions", () => { ]); expect(displayElement.querySelectorAll("p.jspsych-survey-text").length).toBe(2); - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); await expectFinished(); }); test("nested parameter can be a function", async () => { - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: surveyText, questions: [ @@ -95,18 +95,18 @@ describe("nested parameters as functions", () => { }, ]); - expect(document.querySelector("#jspsych-survey-text-0 p.jspsych-survey-text").innerHTML).toBe( + expect(displayElement.querySelector("#jspsych-survey-text-0 p.jspsych-survey-text").innerHTML).toBe( "foo" ); - expect(document.querySelector("#jspsych-survey-text-1 p.jspsych-survey-text").innerHTML).toBe( + expect(displayElement.querySelector("#jspsych-survey-text-1 p.jspsych-survey-text").innerHTML).toBe( "bar" ); - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); await expectFinished(); }); test("multiple nested parameters can be functions", async () => { - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: surveyMultiChoice, questions: [ @@ -128,11 +128,11 @@ describe("nested parameters as functions", () => { }, ]); - expect(document.querySelector("#jspsych-survey-multi-choice-0").innerHTML).toMatch("foo"); - expect(document.querySelector("#jspsych-survey-multi-choice-0").innerHTML).toMatch("buzz"); - expect(document.querySelector("#jspsych-survey-multi-choice-1").innerHTML).toMatch("bar"); - expect(document.querySelector("#jspsych-survey-multi-choice-1").innerHTML).toMatch("one"); - await clickTarget(document.querySelector("#jspsych-survey-multi-choice-next")); + expect(displayElement.querySelector("#jspsych-survey-multi-choice-0").innerHTML).toMatch("foo"); + expect(displayElement.querySelector("#jspsych-survey-multi-choice-0").innerHTML).toMatch("buzz"); + expect(displayElement.querySelector("#jspsych-survey-multi-choice-1").innerHTML).toMatch("bar"); + expect(displayElement.querySelector("#jspsych-survey-multi-choice-1").innerHTML).toMatch("one"); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-choice-next")); await expectFinished(); }); diff --git a/packages/jspsych/tests/core/progressbar.test.ts b/packages/jspsych/tests/core/progressbar.test.ts index 39d1c78a9c..b83d96f993 100644 --- a/packages/jspsych/tests/core/progressbar.test.ts +++ b/packages/jspsych/tests/core/progressbar.test.ts @@ -1,23 +1,29 @@ import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; import { pressKey, startTimeline } from "@jspsych/test-utils"; -import { initJsPsych } from "../../src"; +import { initJsPsych, JsPsych } from "../../src"; + +// progress bar lives in the container element +const getContainer = (jsPsych: JsPsych) => { + return jsPsych.getDisplayContainerElement(); +} describe("automatic progress bar", () => { test("progress bar does not display by default", async () => { - await startTimeline([ + const { jsPsych } = await startTimeline([ { type: htmlKeyboardResponse, stimulus: "foo", }, ]); - expect(document.querySelector("#jspsych-progressbar-container")).toBeNull(); + const displayContainer = getContainer(jsPsych); + expect(displayContainer.querySelector("#jspsych-progressbar-container")).toBeNull(); await pressKey("a"); }); test("progress bar displays when show_progress_bar is true", async () => { - await startTimeline( + const { jsPsych } = await startTimeline( [ { type: htmlKeyboardResponse, @@ -27,7 +33,8 @@ describe("automatic progress bar", () => { { show_progress_bar: true } ); - expect(document.querySelector("#jspsych-progressbar-container").innerHTML).toMatch( + const displayContainer = getContainer(jsPsych); + expect(displayContainer.querySelector("#jspsych-progressbar-container").innerHTML).toMatch( 'Completion Progress
' ); }); @@ -38,9 +45,13 @@ describe("automatic progress bar", () => { stimulus: "foo", }; - await startTimeline([trial, trial, trial, trial], { show_progress_bar: true }); + const { jsPsych } = await startTimeline( + [trial, trial, trial, trial], + { show_progress_bar: true } + ); - const progressbarElement = document.querySelector("#jspsych-progressbar-inner"); + const displayContainer = getContainer(jsPsych); + const progressbarElement = displayContainer.querySelector("#jspsych-progressbar-inner"); expect(progressbarElement.style.width).toEqual("0%"); await pressKey("a"); @@ -59,12 +70,13 @@ describe("automatic progress bar", () => { stimulus: "foo", }; - await startTimeline([trial, trial, trial, trial], { + const { jsPsych } = await startTimeline([trial, trial, trial, trial], { show_progress_bar: true, auto_update_progress_bar: false, }); - const progressbarElement = document.querySelector("#jspsych-progressbar-inner"); + const displayContainer = getContainer(jsPsych); + const progressbarElement = displayContainer.querySelector("#jspsych-progressbar-inner"); for (let i = 0; i < 4; i++) { expect(progressbarElement.style.width).toEqual("0%"); @@ -74,7 +86,7 @@ describe("automatic progress bar", () => { }); test("set `progressBar.progress` manually", async () => { - const jsPsych = initJsPsych({ + const jsPsychObject = initJsPsych({ show_progress_bar: true, auto_update_progress_bar: false, }); @@ -96,9 +108,10 @@ describe("automatic progress bar", () => { }, ]; - await startTimeline(timeline, jsPsych); + const { jsPsych } = await startTimeline(timeline, jsPsychObject); - const progressbarElement = document.querySelector("#jspsych-progressbar-inner"); + const displayContainer = getContainer(jsPsych); + const progressbarElement = displayContainer.querySelector("#jspsych-progressbar-inner"); expect(progressbarElement.style.width).toEqual("0%"); await pressKey("a"); diff --git a/packages/jspsych/tests/data/data-csv-conversion.test.ts b/packages/jspsych/tests/data/data-csv-conversion.test.ts index 133c5f4168..1256fddbf4 100644 --- a/packages/jspsych/tests/data/data-csv-conversion.test.ts +++ b/packages/jspsych/tests/data/data-csv-conversion.test.ts @@ -7,17 +7,17 @@ jest.useFakeTimers(); describe("data conversion to csv", () => { test("survey-text data response object is correctly converted", async () => { - const { getData } = await startTimeline([ + const { getData, displayElement } = await startTimeline([ { type: surveyText, questions: [{ prompt: "Q1" }, { prompt: "Q2" }], }, ]); - document.querySelector("#input-0").value = "Response 1"; - document.querySelector("#input-1").value = "Response 2"; + displayElement.querySelector("#input-0").value = "Response 1"; + displayElement.querySelector("#input-1").value = "Response 2"; - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); expect( getData() @@ -62,7 +62,7 @@ describe("data conversion to csv", () => { }); test("survey-multi-select response array is correctly converted", async () => { - const { getHTML, getData } = await startTimeline([ + const { getHTML, getData, displayElement } = await startTimeline([ { type: surveyMultiSelect, questions: [{ prompt: "foo", options: ["fuzz", "bizz", "bar"], name: "q" }], @@ -70,9 +70,9 @@ describe("data conversion to csv", () => { ]); expect(getHTML()).toMatch("foo"); - await clickTarget(document.querySelector("#jspsych-survey-multi-select-response-0-0")); - await clickTarget(document.querySelector("#jspsych-survey-multi-select-response-0-1")); - await clickTarget(document.querySelector("#jspsych-survey-multi-select-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-select-response-0-0")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-select-response-0-1")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-select-next")); expect(getHTML()).toBe(""); expect( diff --git a/packages/jspsych/tests/data/data-json-conversion.test.ts b/packages/jspsych/tests/data/data-json-conversion.test.ts index 5d58d902e7..1115dbaea7 100644 --- a/packages/jspsych/tests/data/data-json-conversion.test.ts +++ b/packages/jspsych/tests/data/data-json-conversion.test.ts @@ -8,17 +8,17 @@ jest.useFakeTimers(); describe("data conversion to json", () => { test("survey-text data response object is correctly converted", async () => { - const { getData } = await startTimeline([ + const { getData, displayElement } = await startTimeline([ { type: surveyText, questions: [{ prompt: "Q1" }, { prompt: "Q2" }], }, ]); - document.querySelector("#input-0").value = "Response 1"; - document.querySelector("#input-1").value = "Response 2"; + displayElement.querySelector("#input-0").value = "Response 1"; + displayElement.querySelector("#input-1").value = "Response 2"; - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); expect( getData() @@ -71,7 +71,7 @@ describe("data conversion to json", () => { }); test("survey-multi-select response array is correctly converted", async () => { - const { getHTML, getData } = await startTimeline([ + const { getHTML, getData, displayElement } = await startTimeline([ { type: surveyMultiSelect, questions: [{ prompt: "foo", options: ["fuzz", "bizz", "bar"], name: "q" }], @@ -79,9 +79,9 @@ describe("data conversion to json", () => { ]); expect(getHTML()).toMatch("foo"); - await clickTarget(document.querySelector("#jspsych-survey-multi-select-response-0-0")); - await clickTarget(document.querySelector("#jspsych-survey-multi-select-response-0-1")); - await clickTarget(document.querySelector("#jspsych-survey-multi-select-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-select-response-0-0")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-select-response-0-1")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-select-next")); expect(getHTML()).toBe(""); expect( diff --git a/packages/jspsych/tests/data/trialparameters.test.ts b/packages/jspsych/tests/data/trialparameters.test.ts index 3627742ebc..9a5324f4a4 100644 --- a/packages/jspsych/tests/data/trialparameters.test.ts +++ b/packages/jspsych/tests/data/trialparameters.test.ts @@ -65,7 +65,7 @@ describe("Trial parameters in the data", () => { test("Arrayed objects work with save_trial_parameters ", async () => { const questions = [{ prompt: "foo" }, { prompt: "bar" }]; - const { getData } = await startTimeline([ + const { getData, displayElement } = await startTimeline([ { type: surveyText, questions, @@ -75,7 +75,7 @@ describe("Trial parameters in the data", () => { }, ]); - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); const data = getData().values()[0]; expect(data.questions[0].prompt).toBe(questions[0].prompt); @@ -96,7 +96,7 @@ describe("Trial parameters in the data", () => { return html; }; - const { getData } = await startTimeline([ + const { getData, displayElement } = await startTimeline([ { type: reconstruction, stim_function: sample_function, @@ -107,7 +107,7 @@ describe("Trial parameters in the data", () => { }, ]); - await clickTarget(document.querySelector("button")); + await clickTarget(displayElement.querySelector("button")); expect(getData().values()[0].stim_function).toBe(sample_function.toString()); }); diff --git a/packages/plugin-audio-button-response/src/index.spec.ts b/packages/plugin-audio-button-response/src/index.spec.ts index 8a3ff5196a..06a698758b 100644 --- a/packages/plugin-audio-button-response/src/index.spec.ts +++ b/packages/plugin-audio-button-response/src/index.spec.ts @@ -273,9 +273,9 @@ describe("audio-button-response", () => { use_webaudio: false, }); - await startTimeline(timeline, jsPsych); + const { displayElement } = await startTimeline(timeline, jsPsych); - const btns = document.querySelectorAll(".jspsych-html-button-response-button button"); + const btns = displayElement.querySelectorAll(".jspsych-html-button-response-button button"); for (let i = 0; i < btns.length; i++) { expect(btns[i].getAttribute("disabled")).toBe(true); diff --git a/packages/plugin-cloze/src/index.spec.ts b/packages/plugin-cloze/src/index.spec.ts index 045373af89..a08bc22bbe 100644 --- a/packages/plugin-cloze/src/index.spec.ts +++ b/packages/plugin-cloze/src/index.spec.ts @@ -4,13 +4,18 @@ import cloze from "."; jest.useFakeTimers(); -const getInputElementById = (id: string) => document.getElementById(id) as HTMLInputElement; +const getInputElementById = ( + id: string, + displayElement: HTMLElement +) => displayElement.querySelector("#" + id) as HTMLInputElement; -const clickFinishButton = () => clickTarget(document.querySelector("#finish_cloze_button")); +const clickFinishButton = ( + displayElement: HTMLElement +) => clickTarget(displayElement.querySelector("#finish_cloze_button")); describe("cloze", () => { test("displays cloze", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -21,12 +26,12 @@ describe("cloze", () => { '
This is a text.
' ); - await clickFinishButton(); + await clickFinishButton(displayElement); await expectFinished(); }); test("displays default button text", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -37,12 +42,12 @@ describe("cloze", () => { '' ); - await clickFinishButton(); + await clickFinishButton(displayElement); await expectFinished(); }); test("displays custom button text", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -54,24 +59,24 @@ describe("cloze", () => { '' ); - await clickFinishButton(); + await clickFinishButton(displayElement); await expectFinished(); }); test("ends trial on button click when using default settings, i.e. answers are not checked", async () => { - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", }, ]); - await clickFinishButton(); + await clickFinishButton(displayElement); await expectFinished(); }); test("ends trial on button click when answers are checked and correct", async () => { - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -79,13 +84,13 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "cloze"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "cloze"; + await clickFinishButton(displayElement); await expectFinished(); }); test("ends trial on button click when answers are checked and correct without case sensitivity", async () => { - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -94,13 +99,13 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "CLOZE"; - clickTarget(document.querySelector("#finish_cloze_button")); + getInputElementById("input0", displayElement).value = "CLOZE"; + await clickFinishButton(displayElement); await expectFinished(); }); test("ends trial on button click when all answers are checked for completion and are complete", async () => { - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -108,13 +113,13 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "filler"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "filler"; + await clickFinishButton(displayElement); await expectFinished(); }); test("does not end trial on button click when answers are checked and not correct or missing", async () => { - const { expectRunning, expectFinished } = await startTimeline([ + const { expectRunning, expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -122,23 +127,23 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "some wrong answer"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "some wrong answer"; + await clickFinishButton(displayElement); await expectRunning(); - getInputElementById("input0").value = ""; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = ""; + await clickFinishButton(displayElement); await expectRunning(); - getInputElementById("input0").value = "cloze"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "cloze"; + await clickFinishButton(displayElement); await expectFinished(); }); test("does not call mistake function on button click when answers are checked and correct", async () => { const mistakeFn = jest.fn(); - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -147,8 +152,8 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "cloze"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "cloze"; + await clickFinishButton(displayElement); expect(mistakeFn).not.toHaveBeenCalled(); await expectFinished(); @@ -157,7 +162,7 @@ describe("cloze", () => { test("does not call mistake function on button click when answers are checked for completion and are complete", async () => { const mistakeFn = jest.fn(); - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -166,8 +171,8 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "cloze"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "cloze"; + await clickFinishButton(displayElement); expect(mistakeFn).not.toHaveBeenCalled(); await expectFinished(); @@ -176,7 +181,7 @@ describe("cloze", () => { test("calls mistake function on button click when answers are checked and not correct or missing", async () => { const mistakeFn = jest.fn(); - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text.", @@ -185,25 +190,25 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "some wrong answer"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "some wrong answer"; + await clickFinishButton(displayElement); expect(mistakeFn).toHaveBeenCalled(); mistakeFn.mockReset(); - getInputElementById("input0").value = ""; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = ""; + await clickFinishButton(displayElement); expect(mistakeFn).toHaveBeenCalled(); - getInputElementById("input0").value = "cloze"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "cloze"; + await clickFinishButton(displayElement); await expectFinished(); }); test("calls mistake function on button click when answers are checked and do not belong to a multiple answer blank", async () => { const mistakeFn = jest.fn(); - const { expectFinished } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze/jspsych% text.", @@ -212,26 +217,26 @@ describe("cloze", () => { }, ]); - getInputElementById("input0").value = "not fitting in answer"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "not fitting in answer"; + await clickFinishButton(displayElement); expect(mistakeFn).toHaveBeenCalled(); - getInputElementById("input0").value = "cloze"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "cloze"; + await clickFinishButton(displayElement); await expectFinished(); }); test("response data is stored as an array", async () => { - const { getData, expectFinished } = await startTimeline([ + const { getData, expectFinished, displayElement } = await startTimeline([ { type: cloze, text: "This is a %cloze% text. Here is another cloze response box %%.", }, ]); - getInputElementById("input0").value = "cloze1"; - getInputElementById("input1").value = "cloze2"; - await clickFinishButton(); + getInputElementById("input0", displayElement).value = "cloze1"; + getInputElementById("input1", displayElement).value = "cloze2"; + await clickFinishButton(displayElement); await expectFinished(); const data = getData().values()[0].response; diff --git a/packages/plugin-fullscreen/src/index.spec.ts b/packages/plugin-fullscreen/src/index.spec.ts index fac7a44c04..c3cd83ea10 100644 --- a/packages/plugin-fullscreen/src/index.spec.ts +++ b/packages/plugin-fullscreen/src/index.spec.ts @@ -12,7 +12,7 @@ describe("fullscreen plugin", () => { }); test("launches fullscreen mode by default", async () => { - await startTimeline([ + const { displayElement } = await startTimeline([ { type: fullscreen, delay_after: 0, @@ -20,12 +20,12 @@ describe("fullscreen plugin", () => { ]); expect(document.documentElement.requestFullscreen).not.toHaveBeenCalled(); - await clickTarget(document.querySelector("#jspsych-fullscreen-btn")); + await clickTarget(displayElement.querySelector("#jspsych-fullscreen-btn")); expect(document.documentElement.requestFullscreen).toHaveBeenCalled(); }); test("records RT of click", async () => { - const { getData, expectFinished } = await startTimeline([ + const { getData, expectFinished, displayElement } = await startTimeline([ { type: fullscreen, delay_after: 0, @@ -34,7 +34,7 @@ describe("fullscreen plugin", () => { expect(document.documentElement.requestFullscreen).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); - clickTarget(document.querySelector("#jspsych-fullscreen-btn")); + clickTarget(displayElement.querySelector("#jspsych-fullscreen-btn")); expect(document.documentElement.requestFullscreen).toHaveBeenCalled(); jest.runAllTimers(); await expectFinished(); diff --git a/packages/plugin-html-button-response/src/index.spec.ts b/packages/plugin-html-button-response/src/index.spec.ts index c3ff907780..5281906370 100644 --- a/packages/plugin-html-button-response/src/index.spec.ts +++ b/packages/plugin-html-button-response/src/index.spec.ts @@ -142,7 +142,7 @@ describe("html-button-response", () => { }); test("buttons should be disabled first and then enabled after enable_button_after is set", async () => { - const { getHTML } = await startTimeline([ + const { displayElement } = await startTimeline([ { type: htmlButtonResponse, stimulus: "this is html", @@ -151,7 +151,7 @@ describe("html-button-response", () => { }, ]); - const btns = document.querySelectorAll("div#jspsych-html-button-response-btngroup button"); + const btns = displayElement.querySelectorAll("div#jspsych-html-button-response-btngroup button"); expect(btns.length).toBeGreaterThan(0); for (let i = 0; i < btns.length; i++) { diff --git a/packages/plugin-html-keyboard-response/src/index.spec.ts b/packages/plugin-html-keyboard-response/src/index.spec.ts index ce7608a3df..b5a76cbcbd 100644 --- a/packages/plugin-html-keyboard-response/src/index.spec.ts +++ b/packages/plugin-html-keyboard-response/src/index.spec.ts @@ -113,7 +113,7 @@ describe("html-keyboard-response", () => { }); test("class should say responded when key is pressed", async () => { - const { getHTML, expectRunning } = await startTimeline([ + const { getHTML, expectRunning, displayElement } = await startTimeline([ { type: htmlKeyboardResponse, stimulus: "this is html", @@ -128,7 +128,7 @@ describe("html-keyboard-response", () => { await pressKey("f"); - expect(document.querySelector("#jspsych-html-keyboard-response-stimulus").className).toBe( + expect(displayElement.querySelector("#jspsych-html-keyboard-response-stimulus").className).toBe( " responded" ); diff --git a/packages/plugin-html-slider-response/src/index.spec.ts b/packages/plugin-html-slider-response/src/index.spec.ts index 4e85869e09..e336210f7c 100644 --- a/packages/plugin-html-slider-response/src/index.spec.ts +++ b/packages/plugin-html-slider-response/src/index.spec.ts @@ -130,7 +130,7 @@ describe("html-slider-response", () => { }); test("should end trial when button is clicked", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: htmlSliderResponse, stimulus: "this is html", @@ -144,7 +144,7 @@ describe("html-slider-response", () => { '
this is html
' ); - await clickTarget(document.querySelector("#jspsych-html-slider-response-next")); + await clickTarget(displayElement.querySelector("#jspsych-html-slider-response-next")); await expectFinished(); }); diff --git a/packages/plugin-image-button-response/src/index.spec.ts b/packages/plugin-image-button-response/src/index.spec.ts index 83973fa233..3361f7200b 100644 --- a/packages/plugin-image-button-response/src/index.spec.ts +++ b/packages/plugin-image-button-response/src/index.spec.ts @@ -15,7 +15,6 @@ describe("image-button-response", () => { }, ]); - // expect(getHTML()).toContain('
"' ); @@ -68,7 +67,7 @@ describe("image-button-response", () => { }); test("should hide stimulus if stimulus-duration is set", async () => { - const { getHTML, displayElement } = await startTimeline([ + const { displayElement } = await startTimeline([ { type: imageButtonResponse, stimulus: "../media/blue.png", @@ -105,7 +104,7 @@ describe("image-button-response", () => { }); test("should end trial when button is clicked", async () => { - const { getHTML, expectFinished, displayElement } = await startTimeline([ + const { expectFinished, displayElement } = await startTimeline([ { type: imageButtonResponse, stimulus: "../media/blue.png", @@ -138,7 +137,7 @@ describe("image-button-response", () => { }); test("delay enable button", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { displayElement } = await startTimeline([ { type: imageButtonResponse, stimulus: "../media/blue.png", @@ -148,7 +147,7 @@ describe("image-button-response", () => { }, ]); - const btns = document.querySelectorAll(".jspsych-image-button-response-button button"); + const btns = displayElement.querySelectorAll(".jspsych-image-button-response-button button"); for (let i = 0; i < btns.length; i++) { expect(btns[i].getAttribute("disabled")).toBe("disabled"); @@ -200,7 +199,7 @@ describe("image-button-response simulation", () => { }, ]; - const { expectFinished, expectRunning, getHTML, getData } = await simulateTimeline( + const { expectFinished, expectRunning, getData } = await simulateTimeline( timeline, "visual" ); diff --git a/packages/plugin-image-slider-response/src/index.spec.ts b/packages/plugin-image-slider-response/src/index.spec.ts index 7d21ec220a..7e4761452f 100644 --- a/packages/plugin-image-slider-response/src/index.spec.ts +++ b/packages/plugin-image-slider-response/src/index.spec.ts @@ -6,7 +6,7 @@ jest.useFakeTimers(); describe("image-slider-response", () => { test("displays image stimulus", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: imageSliderResponse, stimulus: "../media/blue.png", @@ -19,12 +19,12 @@ describe("image-slider-response", () => { expect(getHTML()).toContain( '
{ - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: imageSliderResponse, stimulus: "../media/blue.png", @@ -37,12 +37,12 @@ describe("image-slider-response", () => { expect(getHTML()).toContain('left'); expect(getHTML()).toContain('right'); - await clickTarget(document.querySelector("#jspsych-image-slider-response-next")); + await clickTarget(displayElement.querySelector("#jspsych-image-slider-response-next")); await expectFinished(); }); test("displays button label", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: imageSliderResponse, stimulus: "../media/blue.png", @@ -56,7 +56,7 @@ describe("image-slider-response", () => { '' ); - await clickTarget(document.querySelector("#jspsych-image-slider-response-next")); + await clickTarget(displayElement.querySelector("#jspsych-image-slider-response-next")); await expectFinished(); }); @@ -82,12 +82,12 @@ describe("image-slider-response", () => { expect(responseElement.max).toBe("10"); expect(responseElement.step).toBe("2"); - await clickTarget(document.querySelector("#jspsych-image-slider-response-next")); + await clickTarget(displayElement.querySelector("#jspsych-image-slider-response-next")); await expectFinished(); }); test("prompt should append to bottom of stimulus", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: imageSliderResponse, stimulus: "../media/blue.png", @@ -100,7 +100,7 @@ describe("image-slider-response", () => { expect(getHTML()).toContain("

This is a prompt

"); - await clickTarget(document.querySelector("#jspsych-image-slider-response-next")); + await clickTarget(displayElement.querySelector("#jspsych-image-slider-response-next")); await expectFinished(); }); @@ -123,7 +123,7 @@ describe("image-slider-response", () => { jest.advanceTimersByTime(500); expect(stimulusElement.style.visibility).toContain("hidden"); - await clickTarget(document.querySelector("#jspsych-image-slider-response-next")); + await clickTarget(displayElement.querySelector("#jspsych-image-slider-response-next")); await expectFinished(); }); @@ -148,7 +148,7 @@ describe("image-slider-response", () => { }); test("should end trial when button is clicked", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: imageSliderResponse, stimulus: "../media/blue.png", @@ -163,7 +163,7 @@ describe("image-slider-response", () => { '
{ test("returns appropriate response with randomization", async () => { - const { getData, expectFinished } = await startTimeline([ + const { getData, expectFinished, displayElement } = await startTimeline([ { type: maxdiff, alternatives: ["a", "b", "c", "d"], @@ -15,10 +15,10 @@ describe("maxdiff plugin", () => { }, ]); - document.querySelector('input[data-name="0"][name="left"]').checked = true; - document.querySelector('input[data-name="1"][name="right"]').checked = true; + displayElement.querySelector('input[data-name="0"][name="left"]').checked = true; + displayElement.querySelector('input[data-name="1"][name="right"]').checked = true; - await clickTarget(document.querySelector("#jspsych-maxdiff-next")); + await clickTarget(displayElement.querySelector("#jspsych-maxdiff-next")); await expectFinished(); expect(getData().values()[0].response).toEqual({ left: "a", right: "b" }); diff --git a/packages/plugin-serial-reaction-time-mouse/src/index.spec.ts b/packages/plugin-serial-reaction-time-mouse/src/index.spec.ts index 98b8e6aa0d..eedab09dc1 100644 --- a/packages/plugin-serial-reaction-time-mouse/src/index.spec.ts +++ b/packages/plugin-serial-reaction-time-mouse/src/index.spec.ts @@ -4,28 +4,31 @@ import serialReactionTimeMouse from "."; jest.useFakeTimers(); -const getCellElement = (cellId: string) => - document.querySelector(`#jspsych-serial-reaction-time-stimulus-cell-${cellId}`) as HTMLElement; +const getCellElement = ( + cellId: string, + displayElement: HTMLElement +) => + displayElement.querySelector(`#jspsych-serial-reaction-time-stimulus-cell-${cellId}`) as HTMLElement; describe("serial-reaction-time-mouse plugin", () => { test("default behavior", async () => { - const { getHTML, expectFinished } = await startTimeline([ + const { getHTML, expectFinished, displayElement } = await startTimeline([ { type: serialReactionTimeMouse, target: [0, 0], }, ]); - expect(getCellElement("0-0").style.backgroundColor).toBe("rgb(153, 153, 153)"); - expect(getCellElement("0-1").style.backgroundColor).toBe(""); - expect(getCellElement("0-2").style.backgroundColor).toBe(""); - expect(getCellElement("0-3").style.backgroundColor).toBe(""); + expect(getCellElement("0-0", displayElement).style.backgroundColor).toBe("rgb(153, 153, 153)"); + expect(getCellElement("0-1", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-2", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-3", displayElement).style.backgroundColor).toBe(""); - mouseDownMouseUpTarget(getCellElement("0-1")); + mouseDownMouseUpTarget(getCellElement("0-1", displayElement)); expect(getHTML()).not.toBe(""); - mouseDownMouseUpTarget(getCellElement("0-0")); + mouseDownMouseUpTarget(getCellElement("0-0", displayElement)); await expectFinished(); }); diff --git a/packages/plugin-serial-reaction-time/src/index.spec.ts b/packages/plugin-serial-reaction-time/src/index.spec.ts index 88890e2d25..bd21edfd5e 100644 --- a/packages/plugin-serial-reaction-time/src/index.spec.ts +++ b/packages/plugin-serial-reaction-time/src/index.spec.ts @@ -4,22 +4,24 @@ import serialReactionTime from "."; jest.useFakeTimers(); -const getCellElement = (cellId: string) => - document.querySelector(`#jspsych-serial-reaction-time-stimulus-cell-${cellId}`) as HTMLElement; +const getCellElement = ( + cellId: string, + displayElement: HTMLElement +) => displayElement.querySelector(`#jspsych-serial-reaction-time-stimulus-cell-${cellId}`) as HTMLElement; describe("serial-reaction-time plugin", () => { test("default behavior", async () => { - const { expectFinished, getData } = await startTimeline([ + const { expectFinished, getData, displayElement } = await startTimeline([ { type: serialReactionTime, target: [0, 0], }, ]); - expect(getCellElement("0-0").style.backgroundColor).toBe("rgb(153, 153, 153)"); - expect(getCellElement("0-1").style.backgroundColor).toBe(""); - expect(getCellElement("0-2").style.backgroundColor).toBe(""); - expect(getCellElement("0-3").style.backgroundColor).toBe(""); + expect(getCellElement("0-0", displayElement).style.backgroundColor).toBe("rgb(153, 153, 153)"); + expect(getCellElement("0-1", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-2", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-3", displayElement).style.backgroundColor).toBe(""); await pressKey("3"); @@ -28,7 +30,7 @@ describe("serial-reaction-time plugin", () => { }); test("response ends trial is false", async () => { - const { getHTML, expectFinished, getData } = await startTimeline([ + const { getHTML, expectFinished, getData, displayElement } = await startTimeline([ { type: serialReactionTime, target: [0, 0], @@ -37,10 +39,10 @@ describe("serial-reaction-time plugin", () => { }, ]); - expect(getCellElement("0-0").style.backgroundColor).toBe("rgb(153, 153, 153)"); - expect(getCellElement("0-1").style.backgroundColor).toBe(""); - expect(getCellElement("0-2").style.backgroundColor).toBe(""); - expect(getCellElement("0-3").style.backgroundColor).toBe(""); + expect(getCellElement("0-0", displayElement).style.backgroundColor).toBe("rgb(153, 153, 153)"); + expect(getCellElement("0-1", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-2", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-3", displayElement).style.backgroundColor).toBe(""); await pressKey("3"); @@ -53,7 +55,7 @@ describe("serial-reaction-time plugin", () => { }); test("responses are scored correctly", async () => { - const { getHTML, expectFinished, getData } = await startTimeline([ + const { displayElement, expectFinished, getData } = await startTimeline([ { type: serialReactionTime, target: [0, 0], @@ -64,19 +66,19 @@ describe("serial-reaction-time plugin", () => { }, ]); - expect(getCellElement("0-0").style.backgroundColor).toBe("rgb(153, 153, 153)"); - expect(getCellElement("0-1").style.backgroundColor).toBe(""); - expect(getCellElement("0-2").style.backgroundColor).toBe(""); - expect(getCellElement("0-3").style.backgroundColor).toBe(""); + expect(getCellElement("0-0", displayElement).style.backgroundColor).toBe("rgb(153, 153, 153)"); + expect(getCellElement("0-1", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-2", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-3", displayElement).style.backgroundColor).toBe(""); await pressKey("3"); jest.runAllTimers(); - expect(getCellElement("0-0").style.backgroundColor).toBe(""); - expect(getCellElement("0-1").style.backgroundColor).toBe("rgb(153, 153, 153)"); - expect(getCellElement("0-2").style.backgroundColor).toBe(""); - expect(getCellElement("0-3").style.backgroundColor).toBe(""); + expect(getCellElement("0-0", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-1", displayElement).style.backgroundColor).toBe("rgb(153, 153, 153)"); + expect(getCellElement("0-2", displayElement).style.backgroundColor).toBe(""); + expect(getCellElement("0-3", displayElement).style.backgroundColor).toBe(""); await pressKey("3"); diff --git a/packages/plugin-survey-html-form/src/index.spec.ts b/packages/plugin-survey-html-form/src/index.spec.ts index 7f8ec0cd5c..e644df690b 100644 --- a/packages/plugin-survey-html-form/src/index.spec.ts +++ b/packages/plugin-survey-html-form/src/index.spec.ts @@ -22,7 +22,7 @@ describe("survey-html-form plugin", () => { '#jspsych-survey-html-form input[name="second"]' )[0].value = TEST_VALUE; - await clickTarget(document.querySelector("#jspsych-survey-html-form-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-html-form-next")); await expectFinished(); diff --git a/packages/plugin-survey-likert/src/index.spec.ts b/packages/plugin-survey-likert/src/index.spec.ts index e9a5573e17..aea489625a 100644 --- a/packages/plugin-survey-likert/src/index.spec.ts +++ b/packages/plugin-survey-likert/src/index.spec.ts @@ -4,13 +4,16 @@ import surveyLikert from "."; jest.useFakeTimers(); -const selectInput = (name: string, value: string) => - document.querySelector(`input[name="${name}"][value="${value}"]`) as HTMLInputElement; +const selectInput = ( + name: string, + value: string, + displayElement: HTMLElement +) => displayElement.querySelector(`input[name="${name}"][value="${value}"]`) as HTMLInputElement; describe("survey-likert plugin", () => { test("data are logged with the right question when randomize order is true", async () => { const scale = ["a", "b", "c", "d", "e"]; - const { getData, expectFinished } = await startTimeline([ + const { getData, expectFinished, displayElement } = await startTimeline([ { type: surveyLikert, questions: [ @@ -24,13 +27,13 @@ describe("survey-likert plugin", () => { }, ]); - selectInput("Q0", "0").checked = true; - selectInput("Q1", "1").checked = true; - selectInput("Q2", "2").checked = true; - selectInput("Q3", "3").checked = true; - selectInput("Q4", "4").checked = true; + selectInput("Q0", "0", displayElement).checked = true; + selectInput("Q1", "1", displayElement).checked = true; + selectInput("Q2", "2", displayElement).checked = true; + selectInput("Q3", "3", displayElement).checked = true; + selectInput("Q4", "4", displayElement).checked = true; - await clickTarget(document.querySelector("#jspsych-survey-likert-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-likert-next")); await expectFinished(); diff --git a/packages/plugin-survey-multi-choice/src/index.spec.ts b/packages/plugin-survey-multi-choice/src/index.spec.ts index e52f6c0e12..cb952e14fe 100644 --- a/packages/plugin-survey-multi-choice/src/index.spec.ts +++ b/packages/plugin-survey-multi-choice/src/index.spec.ts @@ -5,8 +5,12 @@ import surveyMultiChoice from "."; jest.useFakeTimers(); -const getInputElement = (choiceId: number, value: string) => - document.querySelector( +const getInputElement = ( + choiceId: number, + value: string, + displayElement: HTMLElement +) => + displayElement.querySelector( `#jspsych-survey-multi-choice-${choiceId} input[value="${value}"]` ) as HTMLInputElement; @@ -24,7 +28,7 @@ describe("survey-multi-choice plugin", () => { const jsPsychInst = initJsPsych({ display_element: innerDiv }) const options = ["a", "b", "c"]; - const { getData, expectFinished } = await startTimeline([ + const { displayElement, expectFinished } = await startTimeline([ { type: surveyMultiChoice, questions: [ @@ -34,16 +38,14 @@ describe("survey-multi-choice plugin", () => { }, ], jsPsychInst); - getInputElement(0, "a").checked = true; - await clickTarget(document.querySelector("#jspsych-survey-multi-choice-next")); + getInputElement(0, "a", displayElement).checked = true; + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-choice-next")); await expectFinished(); - }) - test("data are logged with the right question when randomize order is true", async () => { var scale = ["a", "b", "c", "d", "e"]; - const { getData, expectFinished } = await startTimeline([ + const { getData, expectFinished, displayElement } = await startTimeline([ { type: surveyMultiChoice, questions: [ @@ -57,13 +59,13 @@ describe("survey-multi-choice plugin", () => { }, ]); - getInputElement(0, "a").checked = true; - getInputElement(1, "b").checked = true; - getInputElement(2, "c").checked = true; - getInputElement(3, "d").checked = true; - getInputElement(4, "e").checked = true; + getInputElement(0, "a", displayElement).checked = true; + getInputElement(1, "b", displayElement).checked = true; + getInputElement(2, "c", displayElement).checked = true; + getInputElement(3, "d", displayElement).checked = true; + getInputElement(4, "e", displayElement).checked = true; - await clickTarget(document.querySelector("#jspsych-survey-multi-choice-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-choice-next")); await expectFinished(); diff --git a/packages/plugin-survey-multi-select/src/index.spec.ts b/packages/plugin-survey-multi-select/src/index.spec.ts index 8e5c1894ca..b1d7386195 100644 --- a/packages/plugin-survey-multi-select/src/index.spec.ts +++ b/packages/plugin-survey-multi-select/src/index.spec.ts @@ -4,8 +4,12 @@ import surveyMultiSelect from "."; jest.useFakeTimers(); -const getInputElement = (selectId: number, value: string) => - document.querySelector( +const getInputElement = ( + selectId: number, + value: string, + displayElement: HTMLElement +) => + displayElement.querySelector( `#jspsych-survey-multi-select-${selectId} input[value="${value}"]` ) as HTMLInputElement; @@ -43,7 +47,7 @@ describe("survey-multi-select plugin", () => { test("data are logged with the right question when randomize order is true", async () => { const scale = ["a", "b", "c", "d", "e"]; - const { expectFinished, getData } = await startTimeline([ + const { expectFinished, getData, displayElement } = await startTimeline([ { type: surveyMultiSelect, questions: [ @@ -57,13 +61,13 @@ describe("survey-multi-select plugin", () => { }, ]); - getInputElement(0, "a").checked = true; - getInputElement(1, "b").checked = true; - getInputElement(2, "c").checked = true; - getInputElement(3, "d").checked = true; - getInputElement(4, "e").checked = true; + getInputElement(0, "a", displayElement).checked = true; + getInputElement(1, "b", displayElement).checked = true; + getInputElement(2, "c", displayElement).checked = true; + getInputElement(3, "d", displayElement).checked = true; + getInputElement(4, "e", displayElement).checked = true; - await clickTarget(document.querySelector("#jspsych-survey-multi-select-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-multi-select-next")); await expectFinished(); diff --git a/packages/plugin-survey-text/src/index.spec.ts b/packages/plugin-survey-text/src/index.spec.ts index 775e81a835..675e56c71b 100644 --- a/packages/plugin-survey-text/src/index.spec.ts +++ b/packages/plugin-survey-text/src/index.spec.ts @@ -2,8 +2,10 @@ import { clickTarget, simulateTimeline, startTimeline } from "@jspsych/test-util import surveyText from "."; -const selectInput = (inputId: number) => - document.querySelector(`#input-${inputId}`); +const selectInput = ( + inputId: number, + displayElement: HTMLElement +) => displayElement.querySelector(`#input-${inputId}`); jest.useFakeTimers(); @@ -17,10 +19,10 @@ describe("survey-text plugin", () => { ]); expect(displayElement.querySelectorAll("p.jspsych-survey-text").length).toBe(2); - expect(selectInput(0).size).toBe(40); - expect(selectInput(1).size).toBe(40); + expect(selectInput(0, displayElement).size).toBe(40); + expect(selectInput(1, displayElement).size).toBe(40); - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); await expectFinished(); }); @@ -37,10 +39,10 @@ describe("survey-text plugin", () => { ]); expect(displayElement.querySelectorAll("p.jspsych-survey-text").length).toBe(2); - expect(selectInput(0).size).toBe(50); - expect(selectInput(1).size).toBe(20); + expect(selectInput(0, displayElement).size).toBe(50); + expect(selectInput(1, displayElement).size).toBe(20); - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); await expectFinished(); }); @@ -57,16 +59,16 @@ describe("survey-text plugin", () => { ]); expect(displayElement.querySelectorAll("p.jspsych-survey-text").length).toBe(2); - expect(selectInput(0).required).toBe(true); - expect(selectInput(1).required).toBe(false); + expect(selectInput(0, displayElement).required).toBe(true); + expect(selectInput(1, displayElement).required).toBe(false); - selectInput(0).value = "42"; - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + selectInput(0, displayElement).value = "42"; + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); await expectFinished(); }); test("data are logged with the right question when randomize order is true", async () => { - const { expectFinished, getData } = await startTimeline([ + const { expectFinished, getData, displayElement } = await startTimeline([ { type: surveyText, questions: [ @@ -80,13 +82,13 @@ describe("survey-text plugin", () => { }, ]); - selectInput(0).value = "a0"; - selectInput(1).value = "a1"; - selectInput(2).value = "a2"; - selectInput(3).value = "a3"; - selectInput(4).value = "a4"; + selectInput(0, displayElement).value = "a0"; + selectInput(1, displayElement).value = "a1"; + selectInput(2, displayElement).value = "a2"; + selectInput(3, displayElement).value = "a3"; + selectInput(4, displayElement).value = "a4"; - await clickTarget(document.querySelector("#jspsych-survey-text-next")); + await clickTarget(displayElement.querySelector("#jspsych-survey-text-next")); await expectFinished(); diff --git a/packages/plugin-video-button-response/src/index.spec.ts b/packages/plugin-video-button-response/src/index.spec.ts index 762afca62b..e5be8f22c7 100644 --- a/packages/plugin-video-button-response/src/index.spec.ts +++ b/packages/plugin-video-button-response/src/index.spec.ts @@ -23,7 +23,7 @@ describe("video-button-response", () => { await expect(async () => { await jsPsych.run(timeline); - }).rejects.toThrowError(); + }).rejects.toThrow(); }); test("enable buttons during video playing", async () => { @@ -40,9 +40,9 @@ describe("video-button-response", () => { const jsPsych = initJsPsych(); - const { getHTML, finished } = await startTimeline(timeline, jsPsych); + const { displayElement } = await startTimeline(timeline, jsPsych); - const btns = document.querySelectorAll(".jspsych-html-button-response-button button"); + const btns = displayElement.querySelectorAll(".jspsych-html-button-response-button button"); for (let i = 0; i < btns.length; i++) { expect(btns[i].getAttribute("disabled")).toBe(true); From c8bda3c35b333c4e12db9d38a304e5385baae671 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Fri, 10 Jan 2025 01:52:45 -0700 Subject: [PATCH 17/25] reactivate old test, remove deprecated `toThrowError()` calls --- packages/jspsych/src/timeline/Timeline.spec.ts | 4 ++-- packages/jspsych/src/timeline/Trial.spec.ts | 2 +- packages/jspsych/tests/core/timeline-variables.test.ts | 9 ++++----- .../jspsych/tests/randomization/randomization.test.ts | 2 +- .../plugin-video-keyboard-response/src/index.spec.ts | 2 +- packages/plugin-video-slider-response/src/index.spec.ts | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/jspsych/src/timeline/Timeline.spec.ts b/packages/jspsych/src/timeline/Timeline.spec.ts index 88688c37f5..8d6d6743f7 100644 --- a/packages/jspsych/src/timeline/Timeline.spec.ts +++ b/packages/jspsych/src/timeline/Timeline.spec.ts @@ -586,10 +586,10 @@ describe("Timeline", () => { const variable = new TimelineVariable("x"); await timeline.run(); - expect(() => timeline.evaluateTimelineVariable(variable)).toThrowError(""); + expect(() => timeline.evaluateTimelineVariable(variable)).toThrow(); expect(() => (timeline.children[0] as Timeline).evaluateTimelineVariable(variable) - ).toThrowError(""); + ).toThrow(); }); }); }); diff --git a/packages/jspsych/src/timeline/Trial.spec.ts b/packages/jspsych/src/timeline/Trial.spec.ts index e26e02c68c..2a53226257 100644 --- a/packages/jspsych/src/timeline/Trial.spec.ts +++ b/packages/jspsych/src/timeline/Trial.spec.ts @@ -524,7 +524,7 @@ describe("Trial", () => { ); await expect( createTrial({ type: TestPlugin, requiredComplexNested: {} }).run() - ).rejects.toThrowError('"requiredComplexNested.requiredChild" parameter'); + ).rejects.toThrow('"requiredComplexNested.requiredChild" parameter'); }); it("errors on missing parameters nested in `COMPLEX` array parameters", async () => { diff --git a/packages/jspsych/tests/core/timeline-variables.test.ts b/packages/jspsych/tests/core/timeline-variables.test.ts index 7bd1e8cdad..c903dc26f1 100644 --- a/packages/jspsych/tests/core/timeline-variables.test.ts +++ b/packages/jspsych/tests/core/timeline-variables.test.ts @@ -400,8 +400,7 @@ describe("timeline variables are correctly evaluated", () => { }); }); -// using console.warn instead of error for now. plan is to enable this test with version 8. -test.skip("timelineVariable() throws an error when variable doesn't exist", async () => { +test("throws an error when variable doesn't exist", async () => { const jsPsych = initJsPsych(); const { expectFinished } = await startTimeline( [ @@ -411,7 +410,7 @@ test.skip("timelineVariable() throws an error when variable doesn't exist", asyn type: htmlKeyboardResponse, stimulus: "foo", on_start: () => { - expect(() => jsPsych.evaluateTimelineVariable("c")).toThrowError(); + expect(() => jsPsych.evaluateTimelineVariable("c")).toThrow(); }, }, ], @@ -430,7 +429,7 @@ test.skip("timelineVariable() throws an error when variable doesn't exist", asyn await expectFinished(); }); -test("timelineVariable() can fetch a variable called 'data'", async () => { +test("can fetch a variable called 'data'", async () => { const jsPsych = initJsPsych(); const { expectFinished } = await startTimeline( [ @@ -440,7 +439,7 @@ test("timelineVariable() can fetch a variable called 'data'", async () => { type: htmlKeyboardResponse, stimulus: "foo", on_start: () => { - expect(() => jsPsych.evaluateTimelineVariable("data")).not.toThrowError(); + expect(() => jsPsych.evaluateTimelineVariable("data")).not.toThrow(); }, }, ], diff --git a/packages/jspsych/tests/randomization/randomization.test.ts b/packages/jspsych/tests/randomization/randomization.test.ts index ac5d13e89e..7562fe0ca1 100644 --- a/packages/jspsych/tests/randomization/randomization.test.ts +++ b/packages/jspsych/tests/randomization/randomization.test.ts @@ -198,7 +198,7 @@ describe("randomInt", () => { test("setting upper < lower throws an error", () => { expect(() => { randomInt(1, 0); - }).toThrowError(); + }).toThrow(); }); }); diff --git a/packages/plugin-video-keyboard-response/src/index.spec.ts b/packages/plugin-video-keyboard-response/src/index.spec.ts index 8772f81e20..e0ac5f0ac6 100644 --- a/packages/plugin-video-keyboard-response/src/index.spec.ts +++ b/packages/plugin-video-keyboard-response/src/index.spec.ts @@ -23,7 +23,7 @@ describe("video-keyboard-response", () => { await expect(async () => { await jsPsych.run(timeline); - }).rejects.toThrowError(); + }).rejects.toThrow(); }); }); diff --git a/packages/plugin-video-slider-response/src/index.spec.ts b/packages/plugin-video-slider-response/src/index.spec.ts index 3aabbefcae..d5b4662e2c 100644 --- a/packages/plugin-video-slider-response/src/index.spec.ts +++ b/packages/plugin-video-slider-response/src/index.spec.ts @@ -22,7 +22,7 @@ describe("video-slider-response", () => { await expect(async () => { await jsPsych.run(timeline); - }).rejects.toThrowError(); + }).rejects.toThrow(); }); }); From 0b781552904dd502f55c0c2e2a44f0918951d5cb Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:23:01 -0700 Subject: [PATCH 18/25] remove unnecessary helper function --- .../jspsych/tests/core/progressbar.test.ts | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/jspsych/tests/core/progressbar.test.ts b/packages/jspsych/tests/core/progressbar.test.ts index b83d96f993..5ceb35bbc4 100644 --- a/packages/jspsych/tests/core/progressbar.test.ts +++ b/packages/jspsych/tests/core/progressbar.test.ts @@ -3,11 +3,6 @@ import { pressKey, startTimeline } from "@jspsych/test-utils"; import { initJsPsych, JsPsych } from "../../src"; -// progress bar lives in the container element -const getContainer = (jsPsych: JsPsych) => { - return jsPsych.getDisplayContainerElement(); -} - describe("automatic progress bar", () => { test("progress bar does not display by default", async () => { const { jsPsych } = await startTimeline([ @@ -17,8 +12,8 @@ describe("automatic progress bar", () => { }, ]); - const displayContainer = getContainer(jsPsych); - expect(displayContainer.querySelector("#jspsych-progressbar-container")).toBeNull(); + expect(jsPsych.getDisplayContainerElement().querySelector("#jspsych-progressbar-container")) + .toBeNull(); await pressKey("a"); }); @@ -33,10 +28,10 @@ describe("automatic progress bar", () => { { show_progress_bar: true } ); - const displayContainer = getContainer(jsPsych); - expect(displayContainer.querySelector("#jspsych-progressbar-container").innerHTML).toMatch( - 'Completion Progress
' - ); + expect(jsPsych.getDisplayContainerElement().querySelector("#jspsych-progressbar-container").innerHTML) + .toMatch( + 'Completion Progress
' + ); }); test("progress bar automatically updates by default", async () => { @@ -50,8 +45,8 @@ describe("automatic progress bar", () => { { show_progress_bar: true } ); - const displayContainer = getContainer(jsPsych); - const progressbarElement = displayContainer.querySelector("#jspsych-progressbar-inner"); + const progressbarElement = + jsPsych.getDisplayContainerElement().querySelector("#jspsych-progressbar-inner"); expect(progressbarElement.style.width).toEqual("0%"); await pressKey("a"); @@ -75,8 +70,8 @@ describe("automatic progress bar", () => { auto_update_progress_bar: false, }); - const displayContainer = getContainer(jsPsych); - const progressbarElement = displayContainer.querySelector("#jspsych-progressbar-inner"); + const progressbarElement = + jsPsych.getDisplayContainerElement().querySelector("#jspsych-progressbar-inner"); for (let i = 0; i < 4; i++) { expect(progressbarElement.style.width).toEqual("0%"); @@ -110,8 +105,8 @@ describe("automatic progress bar", () => { const { jsPsych } = await startTimeline(timeline, jsPsychObject); - const displayContainer = getContainer(jsPsych); - const progressbarElement = displayContainer.querySelector("#jspsych-progressbar-inner"); + const progressbarElement = + jsPsych.getDisplayContainerElement().querySelector("#jspsych-progressbar-inner"); expect(progressbarElement.style.width).toEqual("0%"); await pressKey("a"); From 10afbfc84732a8024b7578e81d1b6658fec2e42e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Jan 2025 21:28:32 +0000 Subject: [PATCH 19/25] chore(release): version packages --- .changeset/tender-ads-prove.md | 5 ----- package-lock.json | 2 +- packages/config/CHANGELOG.md | 6 ++++++ packages/config/package.json | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/tender-ads-prove.md diff --git a/.changeset/tender-ads-prove.md b/.changeset/tender-ads-prove.md deleted file mode 100644 index a0548b90c3..0000000000 --- a/.changeset/tender-ads-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@jspsych/config": patch ---- - -remove DOM clearing after each individual test, fixes issues with testing in other repositories diff --git a/package-lock.json b/package-lock.json index 97a89a4300..5da89c7606 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16061,7 +16061,7 @@ }, "packages/config": { "name": "@jspsych/config", - "version": "3.2.0", + "version": "3.2.1", "license": "MIT", "dependencies": { "@citation-js/core": "^0.7.14", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 1600786f04..0dbd5af37d 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,11 @@ # @jspsych/config +## 3.2.1 + +### Patch Changes + +- [#3486](https://github.com/jspsych/jsPsych/pull/3486) [`ad1d854f43c1e25ba988a3aa2a23a8ab22be3535`](https://github.com/jspsych/jsPsych/commit/ad1d854f43c1e25ba988a3aa2a23a8ab22be3535) Thanks [@jadeddelta](https://github.com/jadeddelta)! - remove DOM clearing after each individual test, fixes issues with testing in other repositories + ## 3.2.0 ### Minor Changes diff --git a/packages/config/package.json b/packages/config/package.json index a26543a27b..c36154b4d5 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@jspsych/config", - "version": "3.2.0", + "version": "3.2.1", "description": "Shared (build) configuration for jsPsych packages", "type": "module", "exports": { From 50a3f208ea8f1046b6d004627398ed8627e75b43 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Fri, 17 Jan 2025 10:08:28 -0500 Subject: [PATCH 20/25] Avoid running action more than once when push to a PR --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0b1bfdd77..cb156b7e80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,8 @@ on: [push, pull_request] jobs: test: name: Build, lint, and test on Node.js ${{ matrix.node }} - + # Prevent the action running twice when the event is both a push and pull request + if: github.event_name != 'push' || github.event_name == 'push && !github.event.pull_request runs-on: ubuntu-latest strategy: matrix: From 846602b45f68d91b2347fe1d796732900afd7bd7 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Fri, 17 Jan 2025 10:09:48 -0500 Subject: [PATCH 21/25] Fix typo --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb156b7e80..cc137c9246 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ jobs: test: name: Build, lint, and test on Node.js ${{ matrix.node }} # Prevent the action running twice when the event is both a push and pull request - if: github.event_name != 'push' || github.event_name == 'push && !github.event.pull_request + if: github.event_name != 'push' || github.event_name == 'push' && !github.event.pull_request runs-on: ubuntu-latest strategy: matrix: From df83261d745b5d114279fa064f3e950e15c6d445 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Fri, 17 Jan 2025 11:02:29 -0500 Subject: [PATCH 22/25] Use `concurrency` instead of `if` --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc137c9246..a75f98fbd6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,11 +2,13 @@ name: build on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: name: Build, lint, and test on Node.js ${{ matrix.node }} - # Prevent the action running twice when the event is both a push and pull request - if: github.event_name != 'push' || github.event_name == 'push' && !github.event.pull_request runs-on: ubuntu-latest strategy: matrix: From 6079c29522aa803a933fea17a689464272b22999 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Fri, 17 Jan 2025 11:11:55 -0500 Subject: [PATCH 23/25] Use branch instead of ref --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a75f98fbd6..4793ea8ad4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: build on: [push, pull_request] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref_name }} cancel-in-progress: true jobs: From 14b2586db193d024d42cce9a8dda1c682846cf7e Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Fri, 17 Jan 2025 11:29:47 -0500 Subject: [PATCH 24/25] Try a different conditional --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4793ea8ad4..e875fba7dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,12 +2,9 @@ name: build on: [push, pull_request] -concurrency: - group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref_name }} - cancel-in-progress: true - jobs: test: + if: github.event_name == 'push' && !contains(github.ref, '/merge') || github.event_name == 'pull_request' name: Build, lint, and test on Node.js ${{ matrix.node }} runs-on: ubuntu-latest strategy: From 236657da5687414a4f2ebf706ce7009e3094d7aa Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Fri, 17 Jan 2025 11:37:20 -0500 Subject: [PATCH 25/25] Change approach again, use `types` to limit which pull requests trigger the event --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e875fba7dc..0c79eed90c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,12 @@ name: build -on: [push, pull_request] +on: + push: + pull_request: + types: [opened, reopened, ready_for_review, review_requested] jobs: test: - if: github.event_name == 'push' && !contains(github.ref, '/merge') || github.event_name == 'pull_request' name: Build, lint, and test on Node.js ${{ matrix.node }} runs-on: ubuntu-latest strategy: