mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-12 08:38:11 +00:00
Merge branch 'master' of https://github.com/jspsych/jsPsych
This commit is contained in:
commit
645102b8cb
6
.github/workflows/jest.yml
vendored
6
.github/workflows/jest.yml
vendored
@ -3,11 +3,7 @@
|
|||||||
|
|
||||||
name: Jest Test
|
name: Jest Test
|
||||||
|
|
||||||
on:
|
on: [ push, pull_request ]
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -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.
|
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.
|
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 `<body>` element, and the width will be 100% of the screen size.
|
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 `<body>` 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.
|
Possible values for the exclusions parameter above.
|
||||||
|
|
||||||
|
@ -381,7 +381,7 @@ var too_long = jsPsych.data.get().filterCustom(function(trial){
|
|||||||
|
|
||||||
#### .first() / .last()
|
#### .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
|
```js
|
||||||
var first_trial = jsPsych.data.get().first(1);
|
var first_trial = jsPsych.data.get().first(1);
|
||||||
|
@ -13,7 +13,7 @@ jsPsych.turk.submitToTurk(data)
|
|||||||
|
|
||||||
Parameter | Type | Description
|
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
|
### Return value
|
||||||
|
|
||||||
|
102
jspsych.js
102
jspsych.js
@ -57,6 +57,10 @@ window.jsPsych = (function() {
|
|||||||
console.error('No timeline declared in jsPsych.init. Cannot start experiment.')
|
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
|
// reset variables
|
||||||
timeline = null;
|
timeline = null;
|
||||||
global_trial_index = 0;
|
global_trial_index = 0;
|
||||||
@ -101,6 +105,7 @@ window.jsPsych = (function() {
|
|||||||
'max_load_time': 60000,
|
'max_load_time': 60000,
|
||||||
'max_preload_attempts': 10,
|
'max_preload_attempts': 10,
|
||||||
'default_iti': 0,
|
'default_iti': 0,
|
||||||
|
'minimum_valid_rt': 0,
|
||||||
'experiment_width': null
|
'experiment_width': null
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -577,8 +582,16 @@ window.jsPsych = (function() {
|
|||||||
// if progress.current_location is -1, then the timeline variable is being evaluated
|
// 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
|
// 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
|
// as being the active trial for purposes of finding the value of the timeline variable
|
||||||
var loc = Math.max(0, progress.current_location);
|
var loc = Math.max(0, progress.current_location);
|
||||||
return timeline_parameters.timeline[loc].timelineVariable(variable_name);
|
// 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].type == jsPsych.plugins.parameterType.COMPLEX){
|
||||||
if(jsPsych.plugins[trial.type].info.parameters[param].array == true){
|
if(jsPsych.plugins[trial.type].info.parameters[param].array == true){
|
||||||
// iterate over each entry in the array
|
// 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
|
// check each parameter in the plugin description
|
||||||
for(var p in jsPsych.plugins[trial.type].info.parameters[param].nested){
|
for(var p in jsPsych.plugins[trial.type].info.parameters[param].nested){
|
||||||
if(typeof trial[param][i][p] == 'undefined' || trial[param][i][p] === null){
|
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:
|
// 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){
|
data_collection.first = function(n){
|
||||||
if(typeof n=='undefined'){ n = 1 }
|
if (typeof n == 'undefined') { n = 1 }
|
||||||
var out = [];
|
if (n < 1) {
|
||||||
for(var i=0; i<n; i++){
|
throw `You must query with a positive nonzero integer. Please use a
|
||||||
out.push(trials[i]);
|
different value for n.`;
|
||||||
}
|
}
|
||||||
return DataCollection(out);
|
if (trials.length == 0) return DataCollection([]);
|
||||||
|
if (n > trials.length) n = trials.length;
|
||||||
|
return DataCollection(trials.slice(0, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
data_collection.last = function(n){
|
/**
|
||||||
if(typeof n=='undefined'){ n = 1 }
|
* Queries the last n elements in a collection of trials.
|
||||||
var out = [];
|
*
|
||||||
for(var i=trials.length-n; i<trials.length; i++){
|
* @param {number} n A positive integer of elements to return. A value of
|
||||||
out.push(trials[i]);
|
* n that is less than 1 will throw an error.
|
||||||
|
*
|
||||||
|
* @return {Array} Last n objects of a collection of trials. If fewer than
|
||||||
|
* n trials are available, the trials.length elements will
|
||||||
|
* be returned.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
data_collection.last = function(n) {
|
||||||
|
if (typeof n == 'undefined') { n = 1 }
|
||||||
|
if (n < 1) {
|
||||||
|
throw `You must query with a positive nonzero integer. Please use a
|
||||||
|
different value for n.`;
|
||||||
}
|
}
|
||||||
return DataCollection(out);
|
if (trials.length == 0) return DataCollection([]);
|
||||||
|
if (n > trials.length) n = trials.length;
|
||||||
|
return DataCollection(trials.slice(trials.length - n, trials.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
data_collection.values = function(){
|
data_collection.values = function(){
|
||||||
@ -2001,6 +2040,7 @@ jsPsych.pluginAPI = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.getKeyboardResponse = function(parameters) {
|
module.getKeyboardResponse = function(parameters) {
|
||||||
|
|
||||||
//parameters are: callback_function, valid_responses, rt_method, persist, audio_context, audio_context_start_time, allow_held_key?
|
//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;
|
parameters.rt_method = (typeof parameters.rt_method === 'undefined') ? 'performance' : parameters.rt_method;
|
||||||
@ -2012,20 +2052,30 @@ jsPsych.pluginAPI = (function() {
|
|||||||
var start_time;
|
var start_time;
|
||||||
if (parameters.rt_method == 'performance') {
|
if (parameters.rt_method == 'performance') {
|
||||||
start_time = performance.now();
|
start_time = performance.now();
|
||||||
} else if (parameters.rt_method == 'audio') {
|
} else if (parameters.rt_method === 'audio') {
|
||||||
start_time = parameters.audio_context_start_time;
|
start_time = parameters.audio_context_start_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
var listener_id;
|
var listener_id;
|
||||||
|
|
||||||
var listener_function = function(e) {
|
var listener_function = function(e) {
|
||||||
|
|
||||||
var key_time;
|
var key_time;
|
||||||
if (parameters.rt_method == 'performance') {
|
if (parameters.rt_method == 'performance') {
|
||||||
key_time = performance.now();
|
key_time = performance.now();
|
||||||
} else if (parameters.rt_method == 'audio') {
|
} else if (parameters.rt_method === 'audio') {
|
||||||
key_time = parameters.audio_context.currentTime
|
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;
|
var valid_response = false;
|
||||||
if (typeof parameters.valid_responses === 'undefined' || parameters.valid_responses == jsPsych.ALL_KEYS) {
|
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
|
// 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) {
|
if (typeof held_keys[e.keyCode] !== 'undefined' && held_keys[e.keyCode] == true) {
|
||||||
valid_response = false;
|
valid_response = false;
|
||||||
}
|
}
|
||||||
@ -2063,7 +2113,7 @@ jsPsych.pluginAPI = (function() {
|
|||||||
|
|
||||||
parameters.callback_function({
|
parameters.callback_function({
|
||||||
key: e.keyCode,
|
key: e.keyCode,
|
||||||
rt: key_time - start_time
|
rt: rt,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (keyboard_listeners.includes(listener_id)) {
|
if (keyboard_listeners.includes(listener_id)) {
|
||||||
@ -2328,15 +2378,16 @@ jsPsych.pluginAPI = (function() {
|
|||||||
function load_audio_file_html5audio(source, count){
|
function load_audio_file_html5audio(source, count){
|
||||||
count = count || 1;
|
count = count || 1;
|
||||||
var audio = new Audio();
|
var audio = new Audio();
|
||||||
audio.addEventListener('canplaythrough', function(){
|
audio.addEventListener('canplaythrough', function handleCanPlayThrough(){
|
||||||
audio_buffers[source] = audio;
|
audio_buffers[source] = audio;
|
||||||
n_loaded++;
|
n_loaded++;
|
||||||
loadfn(n_loaded);
|
loadfn(n_loaded);
|
||||||
if(n_loaded == files.length){
|
if(n_loaded == files.length){
|
||||||
finishfn();
|
finishfn();
|
||||||
}
|
}
|
||||||
|
audio.removeEventListener('canplaythrough', handleCanPlayThrough);
|
||||||
});
|
});
|
||||||
audio.addEventListener('onerror', function(){
|
audio.addEventListener('error', function handleError(){
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
if(count < jsPsych.initSettings().max_preload_attempts){
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
load_audio_file_html5audio(source, count+1)
|
load_audio_file_html5audio(source, count+1)
|
||||||
@ -2344,8 +2395,9 @@ jsPsych.pluginAPI = (function() {
|
|||||||
} else {
|
} else {
|
||||||
jsPsych.loadFail();
|
jsPsych.loadFail();
|
||||||
}
|
}
|
||||||
|
audio.removeEventListener('error', handleError);
|
||||||
});
|
});
|
||||||
audio.addEventListener('onstalled', function(){
|
audio.addEventListener('stalled', function handleStalled(){
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
if(count < jsPsych.initSettings().max_preload_attempts){
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
load_audio_file_html5audio(source, count+1)
|
load_audio_file_html5audio(source, count+1)
|
||||||
@ -2353,8 +2405,9 @@ jsPsych.pluginAPI = (function() {
|
|||||||
} else {
|
} else {
|
||||||
jsPsych.loadFail();
|
jsPsych.loadFail();
|
||||||
}
|
}
|
||||||
|
audio.removeEventListener('stalled', handleStalled);
|
||||||
});
|
});
|
||||||
audio.addEventListener('onabort', function(){
|
audio.addEventListener('abort', function handleAbort(){
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
if(count < jsPsych.initSettings().max_preload_attempts){
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
load_audio_file_html5audio(source, count+1)
|
load_audio_file_html5audio(source, count+1)
|
||||||
@ -2362,6 +2415,7 @@ jsPsych.pluginAPI = (function() {
|
|||||||
} else {
|
} else {
|
||||||
jsPsych.loadFail();
|
jsPsych.loadFail();
|
||||||
}
|
}
|
||||||
|
audio.removeEventListener('abort', handleAbort);
|
||||||
});
|
});
|
||||||
audio.src = source;
|
audio.src = source;
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ jsPsych.plugins["audio-button-response"] = (function() {
|
|||||||
// measure rt
|
// measure rt
|
||||||
var end_time = performance.now();
|
var end_time = performance.now();
|
||||||
var rt = end_time - start_time;
|
var rt = end_time - start_time;
|
||||||
response.button = choice;
|
response.button = parseInt(choice);
|
||||||
response.rt = rt;
|
response.rt = rt;
|
||||||
|
|
||||||
// disable all the buttons after a response
|
// disable all the buttons after a response
|
||||||
|
@ -142,9 +142,9 @@ jsPsych.plugins['audio-slider-response'] = (function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if(trial.require_movement){
|
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').disabled = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
display_element.querySelector('#jspsych-audio-slider-response-next').addEventListener('click', function() {
|
display_element.querySelector('#jspsych-audio-slider-response-next').addEventListener('click', function() {
|
||||||
|
@ -129,7 +129,7 @@ jsPsych.plugins["html-button-response"] = (function() {
|
|||||||
// measure rt
|
// measure rt
|
||||||
var end_time = performance.now();
|
var end_time = performance.now();
|
||||||
var rt = end_time - start_time;
|
var rt = end_time - start_time;
|
||||||
response.button = choice;
|
response.button = parseInt(choice);
|
||||||
response.rt = rt;
|
response.rt = rt;
|
||||||
|
|
||||||
// after a valid response, the stimulus will have the CSS class 'responded'
|
// after a valid response, the stimulus will have the CSS class 'responded'
|
||||||
|
@ -137,9 +137,9 @@ jsPsych.plugins['html-slider-response'] = (function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if(trial.require_movement){
|
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').disabled = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
display_element.querySelector('#jspsych-html-slider-response-next').addEventListener('click', function() {
|
display_element.querySelector('#jspsych-html-slider-response-next').addEventListener('click', function() {
|
||||||
|
@ -163,7 +163,7 @@ jsPsych.plugins["image-button-response"] = (function() {
|
|||||||
// measure rt
|
// measure rt
|
||||||
var end_time = performance.now();
|
var end_time = performance.now();
|
||||||
var rt = end_time - start_time;
|
var rt = end_time - start_time;
|
||||||
response.button = choice;
|
response.button = parseInt(choice);
|
||||||
response.rt = rt;
|
response.rt = rt;
|
||||||
|
|
||||||
// after a valid response, the stimulus will have the CSS class 'responded'
|
// after a valid response, the stimulus will have the CSS class 'responded'
|
||||||
|
@ -172,9 +172,9 @@ jsPsych.plugins['image-slider-response'] = (function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if(trial.require_movement){
|
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').disabled = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
display_element.querySelector('#jspsych-image-slider-response-next').addEventListener('click', function() {
|
display_element.querySelector('#jspsych-image-slider-response-next').addEventListener('click', function() {
|
||||||
|
@ -45,7 +45,7 @@ jsPsych.plugins['same-different-html'] = (function() {
|
|||||||
first_stim_duration: {
|
first_stim_duration: {
|
||||||
type: jsPsych.plugins.parameterType.INT,
|
type: jsPsych.plugins.parameterType.INT,
|
||||||
pretty_name: 'First stimulus duration',
|
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.'
|
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: {
|
gap_duration: {
|
||||||
@ -57,8 +57,8 @@ jsPsych.plugins['same-different-html'] = (function() {
|
|||||||
second_stim_duration: {
|
second_stim_duration: {
|
||||||
type: jsPsych.plugins.parameterType.INT,
|
type: jsPsych.plugins.parameterType.INT,
|
||||||
pretty_name: 'Second stimulus duration',
|
pretty_name: 'Second stimulus duration',
|
||||||
default: 1000,
|
default: null,
|
||||||
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.'
|
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: {
|
prompt: {
|
||||||
type: jsPsych.plugins.parameterType.STRING,
|
type: jsPsych.plugins.parameterType.STRING,
|
||||||
|
@ -47,7 +47,7 @@ jsPsych.plugins['same-different-image'] = (function() {
|
|||||||
first_stim_duration: {
|
first_stim_duration: {
|
||||||
type: jsPsych.plugins.parameterType.INT,
|
type: jsPsych.plugins.parameterType.INT,
|
||||||
pretty_name: 'First stimulus duration',
|
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.'
|
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: {
|
gap_duration: {
|
||||||
@ -59,8 +59,8 @@ jsPsych.plugins['same-different-image'] = (function() {
|
|||||||
second_stim_duration: {
|
second_stim_duration: {
|
||||||
type: jsPsych.plugins.parameterType.INT,
|
type: jsPsych.plugins.parameterType.INT,
|
||||||
pretty_name: 'Second stimulus duration',
|
pretty_name: 'Second stimulus duration',
|
||||||
default: 1000,
|
default: null,
|
||||||
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.'
|
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: {
|
prompt: {
|
||||||
type: jsPsych.plugins.parameterType.STRING,
|
type: jsPsych.plugins.parameterType.STRING,
|
||||||
|
@ -248,7 +248,7 @@ jsPsych.plugins["video-button-response"] = (function() {
|
|||||||
// measure rt
|
// measure rt
|
||||||
var end_time = performance.now();
|
var end_time = performance.now();
|
||||||
var rt = end_time - start_time;
|
var rt = end_time - start_time;
|
||||||
response.button = choice;
|
response.button = parseInt(choice);
|
||||||
response.rt = rt;
|
response.rt = rt;
|
||||||
|
|
||||||
// after a valid response, the stimulus will have the CSS class 'responded'
|
// after a valid response, the stimulus will have the CSS class 'responded'
|
||||||
|
@ -234,9 +234,9 @@ jsPsych.plugins["video-slider-response"] = (function() {
|
|||||||
video_element.playbackRate = trial.rate;
|
video_element.playbackRate = trial.rate;
|
||||||
|
|
||||||
if(trial.require_movement){
|
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;
|
display_element.querySelector('#jspsych-video-slider-response-next').disabled = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var startTime = performance.now();
|
var startTime = performance.now();
|
||||||
|
@ -67,10 +67,32 @@ describe('DataCollection', function(){
|
|||||||
test('#first', function(){
|
test('#first', function(){
|
||||||
expect(jsPsych.data.get().first(3).count()).toBe(3);
|
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(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(){
|
test('#last', function(){
|
||||||
expect(jsPsych.data.get().last(2).count(2)).toBe(2);
|
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(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(){
|
test('#join', function(){
|
||||||
var dc1 = jsPsych.data.get().filter({filter: true});
|
var dc1 = jsPsych.data.get().filter({filter: true});
|
||||||
|
@ -27,4 +27,32 @@ describe('nested defaults', function(){
|
|||||||
expect(display.querySelector('input').placeholder).toBe("")
|
expect(display.querySelector('input').placeholder).toBe("")
|
||||||
expect(display.querySelector('input').size).toBe(40)
|
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();
|
||||||
|
});
|
||||||
})
|
})
|
58
tests/jsPsych/min-rt.test.js
Normal file
58
tests/jsPsych/min-rt.test.js
Normal file
@ -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)
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -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(){
|
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(){
|
describe('add node to end of timeline', function(){
|
||||||
|
|
||||||
test('adds node to end of timeline, without callback', function() {
|
test('adds node to end of timeline, without callback', function() {
|
||||||
var new_trial = {
|
var new_trial = {
|
||||||
type: 'html-keyboard-response',
|
type: 'html-keyboard-response',
|
||||||
stimulus: 'bar'
|
stimulus: 'bar'
|
||||||
};
|
};
|
||||||
|
|
||||||
var new_timeline = {
|
var new_timeline = {
|
||||||
@ -406,4 +494,5 @@ describe('add node to end of timeline', function(){
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -151,4 +151,24 @@ describe('image-button-response', function(){
|
|||||||
|
|
||||||
expect(jsPsych.getDisplayElement().innerHTML).toBe('');
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -123,4 +123,25 @@ describe('image-keyboard-response', function(){
|
|||||||
|
|
||||||
expect(jsPsych.getDisplayElement().innerHTML).toBe('');
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user