diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml
index d5ebea33..753c8626 100644
--- a/.github/workflows/jest.yml
+++ b/.github/workflows/jest.yml
@@ -3,11 +3,7 @@
name: Jest Test
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
+on: [ push, pull_request ]
jobs:
build:
diff --git a/docs/core_library/jspsych-core.md b/docs/core_library/jspsych-core.md
index 4d7b4558..b7b903bb 100644
--- a/docs/core_library/jspsych-core.md
+++ b/docs/core_library/jspsych-core.md
@@ -362,6 +362,7 @@ max_preload_attempts | numeric | The maximum number of attempts to preload each
use_webaudio | boolean | If false, then jsPsych will not attempt to use the WebAudio API for audio playback. Instead, HTML5 Audio objects will be used. The WebAudio API offers more precise control over the timing of audio events, and should be used when possible. The default value is true.
default_iti | numeric | The default inter-trial interval in ms. The default value if none is specified is 0ms.
experiment_width | numeric | The desired width of the jsPsych container in pixels. If left undefined, the width will be 100% of the display element. Usually this is the `
` element, and the width will be 100% of the screen size.
+minimum_valid_rt | numeric | The minimum valid response time for key presses during the experiment. Any key press response time that is less than this value will be treated as invalid and ignored. Note that this parameter only applies to _keyboard responses_, and not to other response types such as buttons and sliders. The default value is 0.
Possible values for the exclusions parameter above.
diff --git a/docs/core_library/jspsych-data.md b/docs/core_library/jspsych-data.md
index 4f390406..31685a4c 100644
--- a/docs/core_library/jspsych-data.md
+++ b/docs/core_library/jspsych-data.md
@@ -381,7 +381,7 @@ var too_long = jsPsych.data.get().filterCustom(function(trial){
#### .first() / .last()
-Returns a DataCollection containing the first/last *n* trials.
+Returns a DataCollection containing the first/last *n* trials. If *n* is greater than the number of trials in the DataCollection, then these functions will return an array of length equal to the number of trials. If there are no trials in the DataCollection, then these functions will return an empty array. If the *n* argument is omitted, then the functions will use the default value of 1. If *n* is zero or a negative number, then these functions will throw an error.
```js
var first_trial = jsPsych.data.get().first(1);
diff --git a/docs/core_library/jspsych-turk.md b/docs/core_library/jspsych-turk.md
index a4bb9f60..cbf3a637 100644
--- a/docs/core_library/jspsych-turk.md
+++ b/docs/core_library/jspsych-turk.md
@@ -13,7 +13,7 @@ jsPsych.turk.submitToTurk(data)
Parameter | Type | Description
----------|------|------------
-data | object | The `data` parameter is an object of `key: value` pairs. Any pairs in the `data` parameter will be saved by Mechanical Turk, and can be downloaded in a CSV file through the Mechanical Turk interface.
+data | object | The `data` parameter is an object of `key: value` pairs. Any pairs in the `data` parameter will be saved by Mechanical Turk, and can be downloaded in a CSV file through the Mechanical Turk interface. **Important**: the `data` parameter must contain at least one `key: value` pair, even just a dummy value, or the HIT will not be submitted correctly.
### Return value
diff --git a/jspsych.js b/jspsych.js
index 53c06fe5..ce82e638 100755
--- a/jspsych.js
+++ b/jspsych.js
@@ -57,6 +57,10 @@ window.jsPsych = (function() {
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;
@@ -101,6 +105,7 @@ window.jsPsych = (function() {
'max_load_time': 60000,
'max_preload_attempts': 10,
'default_iti': 0,
+ 'minimum_valid_rt': 0,
'experiment_width': null
};
@@ -577,8 +582,16 @@ window.jsPsych = (function() {
// if progress.current_location is -1, then the timeline variable is being evaluated
// in a function that runs prior to the trial starting, so we should treat that trial
// as being the active trial for purposes of finding the value of the timeline variable
- var loc = Math.max(0, progress.current_location);
- return timeline_parameters.timeline[loc].timelineVariable(variable_name);
+ var loc = Math.max(0, progress.current_location);
+ // if loc is greater than the number of elements on this timeline, then the timeline
+ // variable is being evaluated in a function that runs after the trial on the timeline
+ // are complete but before advancing to the next (like a loop_function).
+ // treat the last active trial as the active trial for this purpose.
+ if(loc == timeline_parameters.timeline.length){
+ loc = loc - 1;
+ }
+ // now find the variable
+ return timeline_parameters.timeline[loc].timelineVariable(variable_name);
}
}
@@ -922,7 +935,7 @@ window.jsPsych = (function() {
if(jsPsych.plugins[trial.type].info.parameters[param].type == jsPsych.plugins.parameterType.COMPLEX){
if(jsPsych.plugins[trial.type].info.parameters[param].array == true){
// iterate over each entry in the array
- for(var i in trial[param]){
+ trial[param].forEach(function(ip, i){
// check each parameter in the plugin description
for(var p in jsPsych.plugins[trial.type].info.parameters[param].nested){
if(typeof trial[param][i][p] == 'undefined' || trial[param][i][p] === null){
@@ -933,7 +946,7 @@ window.jsPsych = (function() {
}
}
}
- }
+ });
}
}
// if it's not nested, checking is much easier and do that here:
@@ -1129,22 +1142,48 @@ jsPsych.data = (function() {
}
}
+ /**
+ * Queries the first n elements in a collection of trials.
+ *
+ * @param {number} n A positive integer of elements to return. A value of
+ * n that is less than 1 will throw an error.
+ *
+ * @return {Array} First n objects of a collection of trials. If fewer than
+ * n trials are available, the trials.length elements will
+ * be returned.
+ *
+ */
data_collection.first = function(n){
- if(typeof n=='undefined'){ n = 1 }
- var out = [];
- for(var i=0; i trials.length) n = trials.length;
+ return DataCollection(trials.slice(0, n));
}
- data_collection.last = function(n){
- if(typeof n=='undefined'){ n = 1 }
- var out = [];
- for(var i=trials.length-n; i trials.length) n = trials.length;
+ return DataCollection(trials.slice(trials.length - n, trials.length));
}
data_collection.values = function(){
@@ -2001,6 +2040,7 @@ jsPsych.pluginAPI = (function() {
}
module.getKeyboardResponse = function(parameters) {
+
//parameters are: callback_function, valid_responses, rt_method, persist, audio_context, audio_context_start_time, allow_held_key?
parameters.rt_method = (typeof parameters.rt_method === 'undefined') ? 'performance' : parameters.rt_method;
@@ -2012,20 +2052,30 @@ jsPsych.pluginAPI = (function() {
var start_time;
if (parameters.rt_method == 'performance') {
start_time = performance.now();
- } else if (parameters.rt_method == 'audio') {
+ } else if (parameters.rt_method === 'audio') {
start_time = parameters.audio_context_start_time;
}
var listener_id;
var listener_function = function(e) {
-
var key_time;
if (parameters.rt_method == 'performance') {
key_time = performance.now();
- } else if (parameters.rt_method == 'audio') {
+ } else if (parameters.rt_method === 'audio') {
key_time = parameters.audio_context.currentTime
}
+ var rt = key_time - start_time;
+
+ // overiding via parameters for testing purposes.
+ var minimum_valid_rt = parameters.minimum_valid_rt;
+ if(!minimum_valid_rt){
+ minimum_valid_rt = jsPsych.initSettings().minimum_valid_rt || 0;
+ }
+
+ if(rt < minimum_valid_rt){
+ return;
+ }
var valid_response = false;
if (typeof parameters.valid_responses === 'undefined' || parameters.valid_responses == jsPsych.ALL_KEYS) {
@@ -2050,7 +2100,7 @@ jsPsych.pluginAPI = (function() {
}
// check if key was already held down
- if (((typeof parameters.allow_held_key == 'undefined') || !parameters.allow_held_key) && valid_response) {
+ if (((typeof parameters.allow_held_key === 'undefined') || !parameters.allow_held_key) && valid_response) {
if (typeof held_keys[e.keyCode] !== 'undefined' && held_keys[e.keyCode] == true) {
valid_response = false;
}
@@ -2063,7 +2113,7 @@ jsPsych.pluginAPI = (function() {
parameters.callback_function({
key: e.keyCode,
- rt: key_time - start_time
+ rt: rt,
});
if (keyboard_listeners.includes(listener_id)) {
@@ -2328,15 +2378,16 @@ jsPsych.pluginAPI = (function() {
function load_audio_file_html5audio(source, count){
count = count || 1;
var audio = new Audio();
- audio.addEventListener('canplaythrough', function(){
+ audio.addEventListener('canplaythrough', function handleCanPlayThrough(){
audio_buffers[source] = audio;
n_loaded++;
loadfn(n_loaded);
if(n_loaded == files.length){
finishfn();
}
+ audio.removeEventListener('canplaythrough', handleCanPlayThrough);
});
- audio.addEventListener('onerror', function(){
+ audio.addEventListener('error', function handleError(){
if(count < jsPsych.initSettings().max_preload_attempts){
setTimeout(function(){
load_audio_file_html5audio(source, count+1)
@@ -2344,8 +2395,9 @@ jsPsych.pluginAPI = (function() {
} else {
jsPsych.loadFail();
}
+ audio.removeEventListener('error', handleError);
});
- audio.addEventListener('onstalled', function(){
+ audio.addEventListener('stalled', function handleStalled(){
if(count < jsPsych.initSettings().max_preload_attempts){
setTimeout(function(){
load_audio_file_html5audio(source, count+1)
@@ -2353,8 +2405,9 @@ jsPsych.pluginAPI = (function() {
} else {
jsPsych.loadFail();
}
+ audio.removeEventListener('stalled', handleStalled);
});
- audio.addEventListener('onabort', function(){
+ audio.addEventListener('abort', function handleAbort(){
if(count < jsPsych.initSettings().max_preload_attempts){
setTimeout(function(){
load_audio_file_html5audio(source, count+1)
@@ -2362,6 +2415,7 @@ jsPsych.pluginAPI = (function() {
} else {
jsPsych.loadFail();
}
+ audio.removeEventListener('abort', handleAbort);
});
audio.src = source;
}
diff --git a/plugins/jspsych-audio-button-response.js b/plugins/jspsych-audio-button-response.js
index 6341e2cb..878fbe84 100644
--- a/plugins/jspsych-audio-button-response.js
+++ b/plugins/jspsych-audio-button-response.js
@@ -147,7 +147,7 @@ jsPsych.plugins["audio-button-response"] = (function() {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
- response.button = choice;
+ response.button = parseInt(choice);
response.rt = rt;
// disable all the buttons after a response
diff --git a/plugins/jspsych-audio-slider-response.js b/plugins/jspsych-audio-slider-response.js
index 0c619723..9b37b92d 100644
--- a/plugins/jspsych-audio-slider-response.js
+++ b/plugins/jspsych-audio-slider-response.js
@@ -142,9 +142,9 @@ jsPsych.plugins['audio-slider-response'] = (function() {
};
if(trial.require_movement){
- display_element.querySelector('#jspsych-audio-slider-response-response').addEventListener('change', function(){
+ display_element.querySelector('#jspsych-audio-slider-response-response').addEventListener('click', function(){
display_element.querySelector('#jspsych-audio-slider-response-next').disabled = false;
- })
+ });
}
display_element.querySelector('#jspsych-audio-slider-response-next').addEventListener('click', function() {
diff --git a/plugins/jspsych-html-button-response.js b/plugins/jspsych-html-button-response.js
index 727cfdf3..ea745287 100644
--- a/plugins/jspsych-html-button-response.js
+++ b/plugins/jspsych-html-button-response.js
@@ -129,7 +129,7 @@ jsPsych.plugins["html-button-response"] = (function() {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
- response.button = choice;
+ response.button = parseInt(choice);
response.rt = rt;
// after a valid response, the stimulus will have the CSS class 'responded'
diff --git a/plugins/jspsych-html-slider-response.js b/plugins/jspsych-html-slider-response.js
index 8b229142..1e38408d 100644
--- a/plugins/jspsych-html-slider-response.js
+++ b/plugins/jspsych-html-slider-response.js
@@ -137,9 +137,9 @@ jsPsych.plugins['html-slider-response'] = (function() {
};
if(trial.require_movement){
- display_element.querySelector('#jspsych-html-slider-response-response').addEventListener('change', function(){
+ display_element.querySelector('#jspsych-html-slider-response-response').addEventListener('click', function(){
display_element.querySelector('#jspsych-html-slider-response-next').disabled = false;
- })
+ });
}
display_element.querySelector('#jspsych-html-slider-response-next').addEventListener('click', function() {
diff --git a/plugins/jspsych-image-button-response.js b/plugins/jspsych-image-button-response.js
index 69d39096..f189c4b1 100644
--- a/plugins/jspsych-image-button-response.js
+++ b/plugins/jspsych-image-button-response.js
@@ -163,7 +163,7 @@ jsPsych.plugins["image-button-response"] = (function() {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
- response.button = choice;
+ response.button = parseInt(choice);
response.rt = rt;
// after a valid response, the stimulus will have the CSS class 'responded'
diff --git a/plugins/jspsych-image-slider-response.js b/plugins/jspsych-image-slider-response.js
index e0c94cd9..4448102d 100644
--- a/plugins/jspsych-image-slider-response.js
+++ b/plugins/jspsych-image-slider-response.js
@@ -172,9 +172,9 @@ jsPsych.plugins['image-slider-response'] = (function() {
};
if(trial.require_movement){
- display_element.querySelector('#jspsych-image-slider-response-response').addEventListener('change', function(){
+ display_element.querySelector('#jspsych-image-slider-response-response').addEventListener('click', function(){
display_element.querySelector('#jspsych-image-slider-response-next').disabled = false;
- })
+ });
}
display_element.querySelector('#jspsych-image-slider-response-next').addEventListener('click', function() {
diff --git a/plugins/jspsych-same-different-html.js b/plugins/jspsych-same-different-html.js
index c862a622..be428c38 100644
--- a/plugins/jspsych-same-different-html.js
+++ b/plugins/jspsych-same-different-html.js
@@ -45,7 +45,7 @@ jsPsych.plugins['same-different-html'] = (function() {
first_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'First stimulus duration',
- default: 1000,
+ default: null,
description: 'How long to show the first stimulus for in milliseconds. If null, then the stimulus will remain on the screen until any keypress is made.'
},
gap_duration: {
@@ -57,8 +57,8 @@ jsPsych.plugins['same-different-html'] = (function() {
second_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Second stimulus duration',
- default: 1000,
- description: 'How long to show the second stimulus for in milliseconds. If null, then the stimulus will remain on the screen until any keypress is made.'
+ default: null,
+ description: 'How long to show the second stimulus for in milliseconds. If null, then the stimulus will remain on the screen until a valid response is made.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
diff --git a/plugins/jspsych-same-different-image.js b/plugins/jspsych-same-different-image.js
index cc1466c8..f540af7e 100644
--- a/plugins/jspsych-same-different-image.js
+++ b/plugins/jspsych-same-different-image.js
@@ -47,7 +47,7 @@ jsPsych.plugins['same-different-image'] = (function() {
first_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'First stimulus duration',
- default: 1000,
+ default: null,
description: 'How long to show the first stimulus for in milliseconds. If null, then the stimulus will remain on the screen until any keypress is made.'
},
gap_duration: {
@@ -59,8 +59,8 @@ jsPsych.plugins['same-different-image'] = (function() {
second_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Second stimulus duration',
- default: 1000,
- description: 'How long to show the second stimulus for in milliseconds. If null, then the stimulus will remain on the screen until any keypress is made.'
+ default: null,
+ description: 'How long to show the second stimulus for in milliseconds. If null, then the stimulus will remain on the screen until a valid response is made.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
diff --git a/plugins/jspsych-video-button-response.js b/plugins/jspsych-video-button-response.js
index b34447cb..01944f61 100644
--- a/plugins/jspsych-video-button-response.js
+++ b/plugins/jspsych-video-button-response.js
@@ -248,7 +248,7 @@ jsPsych.plugins["video-button-response"] = (function() {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
- response.button = choice;
+ response.button = parseInt(choice);
response.rt = rt;
// after a valid response, the stimulus will have the CSS class 'responded'
diff --git a/plugins/jspsych-video-slider-response.js b/plugins/jspsych-video-slider-response.js
index 8c040c80..de6c6376 100644
--- a/plugins/jspsych-video-slider-response.js
+++ b/plugins/jspsych-video-slider-response.js
@@ -234,9 +234,9 @@ jsPsych.plugins["video-slider-response"] = (function() {
video_element.playbackRate = trial.rate;
if(trial.require_movement){
- display_element.querySelector('#jspsych-video-slider-response-response').addEventListener('change', function(){
+ display_element.querySelector('#jspsych-video-slider-response-response').addEventListener('click', function(){
display_element.querySelector('#jspsych-video-slider-response-next').disabled = false;
- })
+ });
}
var startTime = performance.now();
diff --git a/tests/jsPsych.data/datacollection.test.js b/tests/jsPsych.data/datacollection.test.js
index cdfdb631..f6160e26 100644
--- a/tests/jsPsych.data/datacollection.test.js
+++ b/tests/jsPsych.data/datacollection.test.js
@@ -67,10 +67,32 @@ describe('DataCollection', function(){
test('#first', function(){
expect(jsPsych.data.get().first(3).count()).toBe(3);
expect(jsPsych.data.get().first(2).values()[1].rt).toBe(200);
+ expect(jsPsych.data.get().first().count()).toBe(1);
+ expect(() => {
+ jsPsych.data.get().first(-1)
+ }).toThrow();
+ expect(() => {
+ jsPsych.data.get().first(0)
+ }).toThrow();
+ expect(jsPsych.data.get().filter({foo: "bar"}).first(1).count()).toBe(0);
+ var n = jsPsych.data.get().count();
+ var too_many = n+1;
+ expect(jsPsych.data.get().first(too_many).count()).toBe(n);
});
test('#last', function(){
expect(jsPsych.data.get().last(2).count(2)).toBe(2);
expect(jsPsych.data.get().last(2).values()[0].rt).toBe(400);
+ expect(jsPsych.data.get().last().count()).toBe(1);
+ expect(() => {
+ jsPsych.data.get().last(-1)
+ }).toThrow();
+ expect(() => {
+ jsPsych.data.get().last(0)
+ }).toThrow();
+ expect(jsPsych.data.get().filter({foo: "bar"}).last(1).count()).toBe(0);
+ var n = jsPsych.data.get().count();
+ var too_many = n+1;
+ expect(jsPsych.data.get().last(too_many).count()).toBe(n);
});
test('#join', function(){
var dc1 = jsPsych.data.get().filter({filter: true});
diff --git a/tests/jsPsych/default-parameters.test.js b/tests/jsPsych/default-parameters.test.js
index 66b9a290..ddc05e4a 100644
--- a/tests/jsPsych/default-parameters.test.js
+++ b/tests/jsPsych/default-parameters.test.js
@@ -27,4 +27,32 @@ describe('nested defaults', function(){
expect(display.querySelector('input').placeholder).toBe("")
expect(display.querySelector('input').size).toBe(40)
});
+
+ test('safe against extending the array.prototype (issue #989)', function(){
+ Array.prototype.qq = jest.fn();
+ const spy = jest.spyOn(console, 'error').mockImplementation();
+
+ var t = {
+ type: 'survey-text',
+ questions: [
+ {
+ prompt: 'Question 1.'
+ },
+ {
+ prompt: 'Question 2.'
+ }
+ ]
+ }
+
+ jsPsych.init({timeline: [t]})
+
+ var display = jsPsych.getDisplayElement();
+
+ expect(display.querySelector('input').placeholder).toBe("")
+ expect(display.querySelector('input').size).toBe(40)
+
+ expect(spy).not.toHaveBeenCalled();
+
+ spy.mockRestore();
+ });
})
\ No newline at end of file
diff --git a/tests/jsPsych/min-rt.test.js b/tests/jsPsych/min-rt.test.js
new file mode 100644
index 00000000..9a329a4a
--- /dev/null
+++ b/tests/jsPsych/min-rt.test.js
@@ -0,0 +1,58 @@
+const root = '../../';
+const utils = require('../testing-utils.js');
+
+// ideally, use fake timers for this test, but 'modern' timers that work
+// with performance.now() break something in the first test. wait for fix?
+//jest.useFakeTimers('modern');
+//jest.useFakeTimers();
+
+beforeEach(function(){
+ require(root + 'jspsych.js');
+ require(root + 'plugins/jspsych-html-keyboard-response.js');
+});
+
+describe('minimum_valid_rt parameter', function(){
+ test('has a default value of 0', function(){
+ var t = {
+ type: 'html-keyboard-response',
+ stimulus: 'foo'
+ }
+
+ var t2 = {
+ type: 'html-keyboard-response',
+ stimulus: 'bar'
+ }
+
+ jsPsych.init({timeline: [t,t2]});
+
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
+ utils.pressKey(32);
+ });
+
+ test('correctly prevents fast responses when set', function(done){
+ var t = {
+ type: 'html-keyboard-response',
+ stimulus: 'foo'
+ }
+
+ var t2 = {
+ type: 'html-keyboard-response',
+ stimulus: 'bar'
+ }
+
+ jsPsych.init({timeline: [t,t2], minimum_valid_rt: 100});
+
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ setTimeout(function(){
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
+ utils.pressKey(32);
+ done();
+ }, 100)
+
+ });
+});
\ No newline at end of file
diff --git a/tests/jsPsych/timelines.test.js b/tests/jsPsych/timelines.test.js
index 196a691a..92bddb57 100644
--- a/tests/jsPsych/timelines.test.js
+++ b/tests/jsPsych/timelines.test.js
@@ -110,6 +110,62 @@ describe('loop function', function(){
});
+ test('timeline variables from nested timelines are available in loop function', function(){
+
+ var counter = 0;
+
+ var trial2 = {
+ type: 'html-keyboard-response',
+ stimulus: jsPsych.timelineVariable('word')
+ }
+
+ var innertimeline = {
+ timeline: [{
+ type: 'html-keyboard-response',
+ stimulus: 'foo'
+ }],
+ loop_function: function(){
+ if(jsPsych.timelineVariable('word', true) == 'b' && counter < 2){
+ counter++;
+ return true;
+ } else {
+ counter = 0;
+ return false;
+ }
+ }
+ }
+
+ var outertimeline = {
+ timeline: [trial2, innertimeline],
+ timeline_variables: [
+ {word: 'a'},
+ {word: 'b'},
+ {word: 'c'}
+ ]
+ }
+
+ jsPsych.init({
+ timeline: [outertimeline]
+ });
+
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('a');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('b');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('c');
+ utils.pressKey(32);
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+ utils.pressKey(32);
+ });
+
});
describe('conditional function', function(){
@@ -348,12 +404,44 @@ describe('endCurrentTimeline', function(){
})
});
+
+describe('nested timelines', function() {
+ test('works without other parameters', function() {
+ var t1 = {
+ type: 'html-keyboard-response',
+ stimulus: 'foo'
+ };
+
+ var t2 = {
+ type: 'html-keyboard-response',
+ stimulus: 'bar'
+ };
+
+ var trials = {
+ timeline: [t1, t2]
+ };
+
+ jsPsych.init({
+ timeline: [trials]
+ });
+
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
+
+ utils.pressKey(32);
+
+ expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
+
+ utils.pressKey(32);
+
+ })
+})
+
describe('add node to end of timeline', function(){
test('adds node to end of timeline, without callback', function() {
var new_trial = {
- type: 'html-keyboard-response',
- stimulus: 'bar'
+ type: 'html-keyboard-response',
+ stimulus: 'bar'
};
var new_timeline = {
@@ -406,4 +494,5 @@ describe('add node to end of timeline', function(){
});
-});
\ No newline at end of file
+});
+
diff --git a/tests/plugins/plugin-image-button-response.test.js b/tests/plugins/plugin-image-button-response.test.js
index fb5eade5..3624f1ee 100644
--- a/tests/plugins/plugin-image-button-response.test.js
+++ b/tests/plugins/plugin-image-button-response.test.js
@@ -151,4 +151,24 @@ describe('image-button-response', function(){
expect(jsPsych.getDisplayElement().innerHTML).toBe('');
});
+
+ test('should show console warning when trial duration is null and response ends trial is false', function() {
+ const spy = jest.spyOn(console, 'warn').mockImplementation();
+
+ var trial = {
+ type: 'image-button-response',
+ stimulus: '../media/blue.png',
+ choices: ['button-choice'],
+ response_ends_trial: false,
+ trial_duration: null
+ };
+
+ jsPsych.init({
+ timeline: [trial],
+ auto_preload: false
+ });
+
+ expect(spy).toHaveBeenCalled();
+ spy.mockRestore();
+ });
});
diff --git a/tests/plugins/plugin-image-keyboard-response.test.js b/tests/plugins/plugin-image-keyboard-response.test.js
index f34ee76d..449686ab 100644
--- a/tests/plugins/plugin-image-keyboard-response.test.js
+++ b/tests/plugins/plugin-image-keyboard-response.test.js
@@ -123,4 +123,25 @@ describe('image-keyboard-response', function(){
expect(jsPsych.getDisplayElement().innerHTML).toBe('');
});
+
+
+ test('should show console warning when trial duration is null and response ends trial is false', function() {
+ const spy = jest.spyOn(console, 'warn').mockImplementation();
+
+ var trial = {
+ type: 'image-keyboard-response',
+ stimulus: '../media/blue.png',
+ choices: ['f','j'],
+ response_ends_trial: false,
+ trial_duration: null
+ };
+
+ jsPsych.init({
+ timeline: [trial],
+ auto_preload: false
+ });
+
+ expect(spy).toHaveBeenCalled();
+ spy.mockRestore();
+ });
});