diff --git a/css/jspsych.css b/css/jspsych.css
index 1e53897a..20d10905 100644
--- a/css/jspsych.css
+++ b/css/jspsych.css
@@ -76,9 +76,17 @@
border-color: #ccc;
}
-.jspsych-btn:hover {
+/* only apply the hover style on devices with a mouse/pointer that can hover - issue #977 */
+@media (hover: hover) {
+ .jspsych-btn:hover {
+ background-color: #ddd;
+ border-color: #aaa;
+ }
+}
+
+.jspsych-btn:active {
background-color: #ddd;
- border-color: #aaa;
+ border-color:#000000;
}
.jspsych-btn:disabled {
diff --git a/docs/plugins/jspsych-video-button-response.md b/docs/plugins/jspsych-video-button-response.md
index 084f30cf..ad0d3fa7 100644
--- a/docs/plugins/jspsych-video-button-response.md
+++ b/docs/plugins/jspsych-video-button-response.md
@@ -8,7 +8,7 @@ Parameters with a default value of *undefined* must be specified. Other paramete
Parameter | Type | Default Value | Description
----------|------|---------------|------------
-sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference.
+sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The plugin does not reliably support .mov files. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference.
choices | array of strings | [] | Labels for the buttons. Each different string in the array will generate a different button.
button_html | HTML string | `''` | A template of HTML for generating the button elements. You can override this to create customized buttons of various kinds. The string `%choice%` will be changed to the corresponding element of the `choices` array. You may also specify an array of strings, if you need different HTML to render for each button. If you do specify an array, the `choices` array and this array must have the same length. The HTML from position 0 in the `button_html` array will be used to create the button for element 0 in the `choices` array, and so on.
margin_vertical | string | '0px' | Vertical margin of the button(s).
diff --git a/docs/plugins/jspsych-video-keyboard-response.md b/docs/plugins/jspsych-video-keyboard-response.md
index d36be8a0..5a19f7a4 100644
--- a/docs/plugins/jspsych-video-keyboard-response.md
+++ b/docs/plugins/jspsych-video-keyboard-response.md
@@ -8,7 +8,7 @@ Parameters with a default value of *undefined* must be specified. Other paramete
Parameter | Type | Default Value | Description
----------|------|---------------|------------
-sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference.
+sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The plugin does not reliably support .mov files. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference.
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 subject is supposed to take (e.g., which key to press).
width | numeric | width of the video file | The width of the video display in pixels.
height | numeric | heigh of the video file | The height of the video display in pixels.
diff --git a/docs/plugins/jspsych-video-slider-response.md b/docs/plugins/jspsych-video-slider-response.md
index a6c012c7..14d56130 100644
--- a/docs/plugins/jspsych-video-slider-response.md
+++ b/docs/plugins/jspsych-video-slider-response.md
@@ -8,7 +8,7 @@ Parameters with a default value of *undefined* must be specified. Other paramete
Parameter | Type | Default Value | Description
----------|------|---------------|------------
-sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference.
+sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The plugin does not reliably support .mov files. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference.
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 subject is supposed to take (e.g., which key to press).
width | numeric | width of the video file | The width of the video display in pixels.
height | numeric | heigh of the video file | The height of the video display in pixels.
diff --git a/examples/jspsych-video-keyboard-response.html b/examples/jspsych-video-keyboard-response.html
index 2f376f37..19f589dd 100644
--- a/examples/jspsych-video-keyboard-response.html
+++ b/examples/jspsych-video-keyboard-response.html
@@ -24,8 +24,8 @@
//height: 600,
autoplay: true,
//controls: true,
- start: 8.75,
- stop: 9,
+ //start: 8,
+ //stop: 9,
rate: 1.5,
//trial_duration: 2000,
//trial_ends_after_video: true,
diff --git a/jspsych.js b/jspsych.js
index ce82e638..3605af65 100755
--- a/jspsych.js
+++ b/jspsych.js
@@ -52,158 +52,166 @@ window.jsPsych = (function() {
//
core.init = function(options) {
-
- if(typeof options.timeline === 'undefined'){
- console.error('No timeline declared in jsPsych.init. Cannot start experiment.')
- }
-
- if(options.timeline.length == 0){
- console.error('No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.')
- }
-
- // reset variables
- timeline = null;
- global_trial_index = 0;
- current_trial = {};
- current_trial_finished = false;
- paused = false;
- waiting = false;
- loaded = false;
- loadfail = false;
- jsPsych.data.reset();
-
- var defaults = {
- 'display_element': undefined,
- 'on_finish': function(data) {
- return undefined;
- },
- 'on_trial_start': function(trial) {
- return undefined;
- },
- 'on_trial_finish': function() {
- return undefined;
- },
- 'on_data_update': function(data) {
- return undefined;
- },
- 'on_interaction_data_update': function(data){
- return undefined;
- },
- 'on_close': function(){
- return undefined;
- },
- 'preload_images': [],
- 'preload_audio': [],
- 'preload_video': [],
- 'use_webaudio': true,
- 'exclusions': {},
- 'show_progress_bar': false,
- 'message_progress_bar': 'Completion Progress',
- 'auto_update_progress_bar': true,
- 'auto_preload': true,
- 'show_preload_progress_bar': true,
- 'max_load_time': 60000,
- 'max_preload_attempts': 10,
- 'default_iti': 0,
- 'minimum_valid_rt': 0,
- 'experiment_width': null
- };
-
- // override default options if user specifies an option
- opts = Object.assign({}, defaults, options);
-
- // set DOM element where jsPsych will render content
- // if undefined, then jsPsych will use the
tag and the entire page
- if(typeof opts.display_element == 'undefined'){
- // check if there is a body element on the page
- var body = document.querySelector('body');
- if (body === null) {
- document.documentElement.appendChild(document.createElement('body'));
+ function init() {
+ if(typeof options.timeline === 'undefined'){
+ console.error('No timeline declared in jsPsych.init. Cannot start experiment.')
}
- // using the full page, so we need the HTML element to
- // have 100% height, and body to be full width and height with
- // no margin
- document.querySelector('html').style.height = '100%';
- document.querySelector('body').style.margin = '0px';
- document.querySelector('body').style.height = '100%';
- document.querySelector('body').style.width = '100%';
- opts.display_element = document.querySelector('body');
- } else {
- // make sure that the display element exists on the page
- var display;
- if (opts.display_element instanceof Element) {
- var display = opts.display_element;
+
+ if(options.timeline.length == 0){
+ console.error('No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.')
+ }
+
+ // reset variables
+ timeline = null;
+ global_trial_index = 0;
+ current_trial = {};
+ current_trial_finished = false;
+ paused = false;
+ waiting = false;
+ loaded = false;
+ loadfail = false;
+ jsPsych.data.reset();
+
+ var defaults = {
+ 'display_element': undefined,
+ 'on_finish': function(data) {
+ return undefined;
+ },
+ 'on_trial_start': function(trial) {
+ return undefined;
+ },
+ 'on_trial_finish': function() {
+ return undefined;
+ },
+ 'on_data_update': function(data) {
+ return undefined;
+ },
+ 'on_interaction_data_update': function(data){
+ return undefined;
+ },
+ 'on_close': function(){
+ return undefined;
+ },
+ 'preload_images': [],
+ 'preload_audio': [],
+ 'preload_video': [],
+ 'use_webaudio': true,
+ 'exclusions': {},
+ 'show_progress_bar': false,
+ 'message_progress_bar': 'Completion Progress',
+ 'auto_update_progress_bar': true,
+ 'auto_preload': true,
+ 'show_preload_progress_bar': true,
+ 'max_load_time': 60000,
+ 'max_preload_attempts': 10,
+ 'default_iti': 0,
+ 'minimum_valid_rt': 0,
+ 'experiment_width': null
+ };
+
+ // override default options if user specifies an option
+ opts = Object.assign({}, defaults, options);
+
+ // set DOM element where jsPsych will render content
+ // if undefined, then jsPsych will use the tag and the entire page
+ if(typeof opts.display_element == 'undefined'){
+ // check if there is a body element on the page
+ var body = document.querySelector('body');
+ if (body === null) {
+ document.documentElement.appendChild(document.createElement('body'));
+ }
+ // using the full page, so we need the HTML element to
+ // have 100% height, and body to be full width and height with
+ // no margin
+ document.querySelector('html').style.height = '100%';
+ document.querySelector('body').style.margin = '0px';
+ document.querySelector('body').style.height = '100%';
+ document.querySelector('body').style.width = '100%';
+ opts.display_element = document.querySelector('body');
} else {
- var display = document.querySelector('#' + opts.display_element);
+ // make sure that the display element exists on the page
+ var display;
+ if (opts.display_element instanceof Element) {
+ var display = opts.display_element;
+ } else {
+ var display = document.querySelector('#' + opts.display_element);
+ }
+ if(display === null) {
+ console.error('The display_element specified in jsPsych.init() does not exist in the DOM.');
+ } else {
+ opts.display_element = display;
+ }
}
- if(display === null) {
- console.error('The display_element specified in jsPsych.init() does not exist in the DOM.');
- } else {
- opts.display_element = display;
- }
- }
- opts.display_element.innerHTML = '
';
+ DOM_container = opts.display_element;
+ DOM_target = document.querySelector('#jspsych-content');
- // add tabIndex attribute to scope event listeners
- opts.display_element.tabIndex = 0;
-
- // add CSS class to DOM_target
- if(opts.display_element.className.indexOf('jspsych-display-element') == -1){
- opts.display_element.className += ' jspsych-display-element';
- }
- DOM_target.className += 'jspsych-content';
-
- // set experiment_width if not null
- if(opts.experiment_width !== null){
- DOM_target.style.width = opts.experiment_width + "px";
- }
-
- // create experiment timeline
- timeline = new TimelineNode({
- timeline: opts.timeline
- });
-
- // initialize audio context based on options and browser capabilities
- jsPsych.pluginAPI.initAudio();
-
- // below code resets event listeners that may have lingered from
- // a previous incomplete experiment loaded in same DOM.
- jsPsych.pluginAPI.reset(opts.display_element);
- // create keyboard event listeners
- jsPsych.pluginAPI.createKeyboardEventListeners(opts.display_element);
- // create listeners for user browser interaction
- jsPsych.data.createInteractionListeners();
-
- // add event for closing window
- window.addEventListener('beforeunload', opts.on_close);
-
- // check exclusions before continuing
- checkExclusions(opts.exclusions,
- function(){
- // success! user can continue...
- // start experiment, with or without preloading
- if(opts.auto_preload){
- jsPsych.pluginAPI.autoPreload(timeline, startExperiment, opts.preload_images, opts.preload_audio, opts.preload_video, opts.show_preload_progress_bar);
- if(opts.max_load_time > 0){
- setTimeout(function(){
- if(!loaded && !loadfail){
- core.loadFail();
- }
- }, opts.max_load_time);
- }
- } else {
- startExperiment();
- }
- },
- function(){
- // fail. incompatible user.
+ // add tabIndex attribute to scope event listeners
+ opts.display_element.tabIndex = 0;
+ // add CSS class to DOM_target
+ if(opts.display_element.className.indexOf('jspsych-display-element') == -1){
+ opts.display_element.className += ' jspsych-display-element';
}
- );
- };
+ DOM_target.className += 'jspsych-content';
+
+ // set experiment_width if not null
+ if(opts.experiment_width !== null){
+ DOM_target.style.width = opts.experiment_width + "px";
+ }
+
+ // create experiment timeline
+ timeline = new TimelineNode({
+ timeline: opts.timeline
+ });
+
+ // initialize audio context based on options and browser capabilities
+ jsPsych.pluginAPI.initAudio();
+
+ // below code resets event listeners that may have lingered from
+ // a previous incomplete experiment loaded in same DOM.
+ jsPsych.pluginAPI.reset(opts.display_element);
+ // create keyboard event listeners
+ jsPsych.pluginAPI.createKeyboardEventListeners(opts.display_element);
+ // create listeners for user browser interaction
+ jsPsych.data.createInteractionListeners();
+
+ // add event for closing window
+ window.addEventListener('beforeunload', opts.on_close);
+
+ // check exclusions before continuing
+ checkExclusions(opts.exclusions,
+ function(){
+ // success! user can continue...
+ // start experiment, with or without preloading
+ if(opts.auto_preload){
+ jsPsych.pluginAPI.autoPreload(timeline, startExperiment, opts.preload_images, opts.preload_audio, opts.preload_video, opts.show_preload_progress_bar);
+ if(opts.max_load_time > 0){
+ setTimeout(function(){
+ if(!loaded && !loadfail){
+ core.loadFail();
+ }
+ }, opts.max_load_time);
+ }
+ } else {
+ startExperiment();
+ }
+ },
+ function(){
+ // fail. incompatible user.
+
+ }
+ );
+ };
+
+ // execute init() when the document is ready
+ if (document.readyState === "complete") {
+ init();
+ } else {
+ window.addEventListener("load", init);
+ }
+ }
core.progress = function() {
@@ -2600,7 +2608,7 @@ jsPsych.pluginAPI = (function() {
var loaded = 0;
if(progress_bar){
- var pb_html = "
";
+ var pb_html = "
";
pb_html += "";
pb_html += "
";
jsPsych.getDisplayElement().innerHTML = pb_html;
diff --git a/plugins/jspsych-video-button-response.js b/plugins/jspsych-video-button-response.js
index e8edd3b7..afcb581c 100644
--- a/plugins/jspsych-video-button-response.js
+++ b/plugins/jspsych-video-button-response.js
@@ -138,12 +138,19 @@ jsPsych.plugins["video-button-response"] = (function() {
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
- if(trial.autoplay){
+ if(trial.autoplay & (trial.start == null)){
+ // if autoplay is true and the start time is specified, then the video will start automatically
+ // via the play() method, rather than the autoplay attribute, to prevent showing the first frame
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
+ if (trial.start !== null) {
+ // hide video element when page loads if the start time is specified,
+ // to prevent the video element from showing the first frame
+ video_html += ' style="visibility: hidden;"';
+ }
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
@@ -155,6 +162,9 @@ jsPsych.plugins["video-button-response"] = (function() {
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
+ if (type == "mov") {
+ console.warn('Warning: video-button-response plugin does not reliably support .mov files.')
+ }
video_html+='';
}
}
@@ -190,11 +200,13 @@ jsPsych.plugins["video-button-response"] = (function() {
var start_time = performance.now();
+ var video_element = display_element.querySelector('#jspsych-video-button-response-stimulus');
+
if(video_preload_blob){
- display_element.querySelector('#jspsych-video-button-response-stimulus').src = video_preload_blob;
+ video_element.src = video_preload_blob;
}
- display_element.querySelector('#jspsych-video-button-response-stimulus').onended = function(){
+ video_element.onended = function(){
if(trial.trial_ends_after_video){
end_trial();
} else if (!trial.response_allowed_while_playing) {
@@ -205,21 +217,30 @@ jsPsych.plugins["video-button-response"] = (function() {
}
}
+ video_element.playbackRate = trial.rate;
+
+ // if video start time is specified, hide the video and set the starting time
+ // before showing and playing, so that the video doesn't automatically show the first frame
if(trial.start !== null){
- display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime = trial.start;
+ video_element.pause();
+ video_element.currentTime = trial.start;
+ video_element.onseeked = function() {
+ video_element.style.visibility = "visible";
+ if (trial.autoplay) {
+ video_element.play();
+ }
+ }
}
if(trial.stop !== null){
- display_element.querySelector('#jspsych-video-button-response-stimulus').addEventListener('timeupdate', function(e){
- var currenttime = display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime;
+ video_element.addEventListener('timeupdate', function(e){
+ var currenttime = video_element.currentTime;
if(currenttime >= trial.stop){
- display_element.querySelector('#jspsych-video-button-response-stimulus').pause();
+ video_element.pause();
}
})
}
- display_element.querySelector('#jspsych-video-button-response-stimulus').playbackRate = trial.rate;
-
// add event listeners to buttons
for (var i = 0; i < trial.choices.length; i++) {
display_element.querySelector('#jspsych-video-button-response-button-' + i).addEventListener('click', function(e){
@@ -273,7 +294,7 @@ jsPsych.plugins["video-button-response"] = (function() {
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
- display_element.querySelector('#jspsych-video-button-response-stimulus').className += ' responded';
+ video_element.className += ' responded';
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-video-button-response-button button');
diff --git a/plugins/jspsych-video-keyboard-response.js b/plugins/jspsych-video-keyboard-response.js
index bb231bf7..57539c4c 100644
--- a/plugins/jspsych-video-keyboard-response.js
+++ b/plugins/jspsych-video-keyboard-response.js
@@ -119,12 +119,19 @@ jsPsych.plugins["video-keyboard-response"] = (function() {
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
- if(trial.autoplay){
+ if(trial.autoplay & (trial.start == null)){
+ // if autoplay is true and the start time is specified, then the video will start automatically
+ // via the play() method, rather than the autoplay attribute, to prevent showing the first frame
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
+ if (trial.start !== null) {
+ // hide video element when page loads if the start time is specified,
+ // to prevent the video element from showing the first frame
+ video_html += ' style="visibility: hidden;"';
+ }
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
@@ -136,6 +143,9 @@ jsPsych.plugins["video-keyboard-response"] = (function() {
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
+ if (type == "mov") {
+ console.warn('Warning: video-keyboard-response plugin does not reliably support .mov files.')
+ }
video_html+='';
}
}
@@ -149,11 +159,13 @@ jsPsych.plugins["video-keyboard-response"] = (function() {
display_element.innerHTML = video_html;
+ var video_element = display_element.querySelector('#jspsych-video-keyboard-response-stimulus');
+
if(video_preload_blob){
- display_element.querySelector('#jspsych-video-keyboard-response-stimulus').src = video_preload_blob;
+ video_element.src = video_preload_blob;
}
- display_element.querySelector('#jspsych-video-keyboard-response-stimulus').onended = function(){
+ video_element.onended = function(){
if(trial.trial_ends_after_video){
end_trial();
}
@@ -168,22 +180,31 @@ jsPsych.plugins["video-keyboard-response"] = (function() {
});
}
}
+
+ video_element.playbackRate = trial.rate;
+ // if video start time is specified, hide the video and set the starting time
+ // before showing and playing, so that the video doesn't automatically show the first frame
if(trial.start !== null){
- display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime = trial.start;
+ video_element.pause();
+ video_element.currentTime = trial.start;
+ video_element.onseeked = function() {
+ video_element.style.visibility = "visible";
+ if (trial.autoplay) {
+ video_element.play();
+ }
+ }
}
if(trial.stop !== null){
- display_element.querySelector('#jspsych-video-keyboard-response-stimulus').addEventListener('timeupdate', function(e){
- var currenttime = display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime;
+ video_element.addEventListener('timeupdate', function(e){
+ var currenttime = video_element.currentTime;
if(currenttime >= trial.stop){
- display_element.querySelector('#jspsych-video-keyboard-response-stimulus').pause();
+ video_element.pause();
}
})
}
- display_element.querySelector('#jspsych-video-keyboard-response-stimulus').playbackRate = trial.rate;
-
// store response
var response = {
rt: null,
diff --git a/plugins/jspsych-video-slider-response.js b/plugins/jspsych-video-slider-response.js
index b73a76ad..205c3fbc 100644
--- a/plugins/jspsych-video-slider-response.js
+++ b/plugins/jspsych-video-slider-response.js
@@ -161,12 +161,19 @@ jsPsych.plugins["video-slider-response"] = (function() {
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
- if(trial.autoplay){
+ if(trial.autoplay & (trial.start == null)){
+ // if autoplay is true and the start time is specified, then the video will start automatically
+ // via the play() method, rather than the autoplay attribute, to prevent showing the first frame
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
+ if (trial.start !== null) {
+ // hide video element when page loads if the start time is specified,
+ // to prevent the video element from showing the first frame
+ video_html += ' style="visibility: hidden;"';
+ }
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
@@ -178,6 +185,9 @@ jsPsych.plugins["video-slider-response"] = (function() {
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
+ if (type == "mov") {
+ console.warn('Warning: video-slider-response plugin does not reliably support .mov files.')
+ }
video_html+='';
}
}
@@ -234,8 +244,19 @@ jsPsych.plugins["video-slider-response"] = (function() {
}
}
+ video_element.playbackRate = trial.rate;
+
+ // if video start time is specified, hide the video and set the starting time
+ // before showing and playing, so that the video doesn't automatically show the first frame
if(trial.start !== null){
+ video_element.pause();
video_element.currentTime = trial.start;
+ video_element.onseeked = function() {
+ video_element.style.visibility = "visible";
+ if (trial.autoplay) {
+ video_element.play();
+ }
+ }
}
if(trial.stop !== null){
@@ -247,8 +268,6 @@ jsPsych.plugins["video-slider-response"] = (function() {
})
}
- video_element.playbackRate = trial.rate;
-
if(trial.require_movement){
display_element.querySelector('#jspsych-video-slider-response-response').addEventListener('click', function(){
display_element.querySelector('#jspsych-video-slider-response-next').disabled = false;
diff --git a/tests/jsPsych/init.test.js b/tests/jsPsych/init.test.js
new file mode 100644
index 00000000..6f3ea688
--- /dev/null
+++ b/tests/jsPsych/init.test.js
@@ -0,0 +1,48 @@
+require("../../jspsych");
+require("../../plugins/jspsych-html-keyboard-response");
+
+describe("jsPsych init", () => {
+ beforeEach(() => {
+ document.body.innerHTML = "";
+ });
+
+ function setReadyState(targetState) {
+ jest
+ .spyOn(document, "readyState", "get")
+ .mockImplementation(() => targetState);
+ }
+
+ function getBodyHTML() {
+ return document.body.innerHTML;
+ }
+
+ function init() {
+ jsPsych.init({
+ timeline: [
+ {
+ type: "html-keyboard-response",
+ stimulus: "foo",
+ },
+ ],
+ });
+ }
+
+ it("should delay execution until the document is ready", () => {
+ expect(getBodyHTML()).toBe("");
+
+ setReadyState("loading");
+ init();
+ expect(getBodyHTML()).toBe("");
+
+ // Simulate the document getting ready
+ setReadyState("complete");
+ window.dispatchEvent(new Event("load"));
+ expect(getBodyHTML()).not.toBe("");
+ });
+
+ it("should execute immediately when the document is ready", () => {
+ // The document is ready by default in jsdom
+ init();
+ expect(getBodyHTML()).not.toBe("");
+ });
+});