mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-12 08:38:11 +00:00
Merge pull request #1497 from jspsych/feature-nested-function-parameters
Merge feature-nested-function-parameters branch - fixes #988
This commit is contained in:
commit
68c4a466ed
@ -100,7 +100,7 @@ var trial = {
|
|||||||
|
|
||||||
## Dynamic parameters
|
## Dynamic parameters
|
||||||
|
|
||||||
Most plugins allow parameters to be functions. In a typical declaration of a jsPsych trial, parameters have to be known at the start of the experiment. This makes it impossible to alter the content of the trial based on the outcome of previous trials. When functions are used as parameters for a block of trials, the function is evaluated at the start of each trial, and the return value of the function is used as the parameter. This enables dynamic updating of the parameter based on data that a subject has generated.
|
Most trial parameters can be functions. In a typical declaration of a jsPsych trial, parameters have to be known at the start of the experiment. This makes it impossible to alter the content of the trial based on the outcome of previous trials. When functions are used as the parameter value, the function is evaluated at the start of the trial, and the return value of the function is used as the parameter for that trial. This enables dynamic updating of the parameter based on data that a subject has generated or any other information that you may not know in advance.
|
||||||
|
|
||||||
Here is a sketch of how this functionality could be used to display feedback to a subject in the Flanker Task.
|
Here is a sketch of how this functionality could be used to display feedback to a subject in the Flanker Task.
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ var trial = {
|
|||||||
},
|
},
|
||||||
on_finish: function(data){
|
on_finish: function(data){
|
||||||
if(data.key_press == "f"){
|
if(data.key_press == "f"){
|
||||||
data.correct = true; // can add property correct by modify data object directly
|
data.correct = true; // can add the property "correct" to the trial data by modifying the data object directly
|
||||||
} else {
|
} else {
|
||||||
data.correct = false;
|
data.correct = false;
|
||||||
}
|
}
|
||||||
@ -128,6 +128,7 @@ var trial = {
|
|||||||
var feedback = {
|
var feedback = {
|
||||||
type: 'html-keyboard-response',
|
type: 'html-keyboard-response',
|
||||||
stimulus: function(){
|
stimulus: function(){
|
||||||
|
// determine what the stimulus should be for this trial based on the accuracy of the last response
|
||||||
var last_trial_correct = jsPsych.data.get().last(1).values()[0].correct;
|
var last_trial_correct = jsPsych.data.get().last(1).values()[0].correct;
|
||||||
if(last_trial_correct){
|
if(last_trial_correct){
|
||||||
return "<p>Correct!</p>";
|
return "<p>Correct!</p>";
|
||||||
@ -140,3 +141,73 @@ var feedback = {
|
|||||||
timeline.push(trial, feedback);
|
timeline.push(trial, feedback);
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The trial's `data` parameter can be also function, which is useful for when you want to save information to the data that can change during the experiment. For example, if you have a global variable called `current_difficulty` that tracks the difficulty level in an adaptive task, you can save the current value of this variable to the trial data like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var current_difficulty; // value changes during the experiment
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'survey-text',
|
||||||
|
questions: [{prompt: "Please enter your response."}]
|
||||||
|
data: function() {
|
||||||
|
return {difficulty: current_difficulty};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It's also possible to use a function for just a single parameter in the trial's `data` object, for instance if you want to combine static and dynamic information in the data:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var trial = {
|
||||||
|
type: 'survey-text',
|
||||||
|
questions: [{prompt: "Please enter your response."}]
|
||||||
|
data: {
|
||||||
|
difficulty: function() {
|
||||||
|
return current_difficulty; // this value changes during the experiment
|
||||||
|
},
|
||||||
|
task_part: 'recall', // this part of the trial data is always the same
|
||||||
|
block_number: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Dyanmic parameters work the same way with nested parameters, which are parameters that contain one or more sets of other parameters. For instance, many survey-* plugins have a `questions` parameter that is a nested parameter: it is an array that contains the parameters for one or more questions on the page. To make the `questions` parameter dynamic, you can use a function that returns the array with all of the parameters for each question:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var subject_id; // value is set during the experiment
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'survey-text',
|
||||||
|
questions: function(){
|
||||||
|
return [ {prompt: "Hi "+subject_id+"! What's your favorite city?", required: true, name: 'fav_city'} ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use a function for any of the individual parameters inside of a nested parameter.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var trial = {
|
||||||
|
type: 'survey-text',
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
prompt: function() {
|
||||||
|
// this question prompt is dynamic -
|
||||||
|
// the text that is shown will change based on the participant's earlier response
|
||||||
|
var favorite_city = JSON.parse(jsPsych.data.getLastTrialData().values()[0].responses).fav_city;
|
||||||
|
var text = "Earlier you said your favorite city is "+favorite_city+". What do you like most about "+favorite_city+"?"
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
rows: 40,
|
||||||
|
columns: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prompt: 'This is the second question the page. This one is not dynamic.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that if the plugin *expects* the value of a given parameter to be a function, then this function *will not* be evaluated at the start of the trial. This is because some plugins allow the researcher to specify functions that should be called at some point during the trial. Some examples of this include the `stimulus` parameter in the canvas-* plugins, the `mistake_fn` parameter in the cloze plugin, and the `stim_function` parameter in the reconstruction plugin. If you want to check whether this is the case for a particular plugin and parameter, then the parameter's `type` in the `plugin.info` section of the plugin file. If the parameter type is `jsPsych.plugins.parameterType.FUNCTION`, then this parameter must be a function and it will not be executed before the trial starts.
|
55
jspsych.js
55
jspsych.js
@ -1013,29 +1013,50 @@ window.jsPsych = (function() {
|
|||||||
// this if statement is checking to see if the parameter type is expected to be a function, in which case we should NOT evaluate it.
|
// this if statement is checking to see if the parameter type is expected to be a function, in which case we should NOT evaluate it.
|
||||||
// the first line checks if the parameter is defined in the universalPluginParameters set
|
// the first line checks if the parameter is defined in the universalPluginParameters set
|
||||||
// the second line checks the plugin-specific parameters
|
// the second line checks the plugin-specific parameters
|
||||||
if(
|
if(typeof jsPsych.plugins.universalPluginParameters[keys[i]] !== 'undefined' &&
|
||||||
(typeof jsPsych.plugins.universalPluginParameters[keys[i]] !== 'undefined' && jsPsych.plugins.universalPluginParameters[keys[i]].type !== jsPsych.plugins.parameterType.FUNCTION ) ||
|
jsPsych.plugins.universalPluginParameters[keys[i]].type !== jsPsych.plugins.parameterType.FUNCTION ){
|
||||||
(typeof jsPsych.plugins[trial.type].info.parameters[keys[i]] !== 'undefined' && jsPsych.plugins[trial.type].info.parameters[keys[i]].type !== jsPsych.plugins.parameterType.FUNCTION)
|
trial[keys[i]] = replaceFunctionsWithValues(trial[keys[i]], null);
|
||||||
) {
|
}
|
||||||
if (typeof trial[keys[i]] == "function") {
|
if(typeof jsPsych.plugins[trial.type].info.parameters[keys[i]] !== 'undefined' &&
|
||||||
trial[keys[i]] = trial[keys[i]].call();
|
jsPsych.plugins[trial.type].info.parameters[keys[i]].type !== jsPsych.plugins.parameterType.FUNCTION){
|
||||||
}
|
trial[keys[i]] = replaceFunctionsWithValues(trial[keys[i]], jsPsych.plugins[trial.type].info.parameters[keys[i]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add a special exception for the data parameter so we can evaluate functions. eventually this could be generalized so that any COMPLEX object type could
|
}
|
||||||
// be evaluated at the individual parameter level.
|
// reset so jsPsych.timelineVariable() is no longer immediately executed
|
||||||
if(keys[i] == 'data'){
|
jsPsych.internal.call_immediate = false;
|
||||||
var data_params = Object.keys(trial[keys[i]]);
|
}
|
||||||
for(var j=0; j<data_params.length; j++){
|
|
||||||
if(typeof trial[keys[i]][data_params[j]] == "function") {
|
function replaceFunctionsWithValues(obj, info){
|
||||||
trial[keys[i]][data_params[j]] = trial[keys[i]][data_params[j]].call();
|
// null typeof is 'object' (?!?!), so need to run this first!
|
||||||
|
if(obj === null){
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
// arrays
|
||||||
|
else if(Array.isArray(obj)){
|
||||||
|
for(var i=0; i<obj.length; i++){
|
||||||
|
obj[i] = replaceFunctionsWithValues(obj[i], info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// objects
|
||||||
|
else if(typeof obj === 'object'){
|
||||||
|
var keys = Object.keys(obj);
|
||||||
|
if(info == null || !info.nested){
|
||||||
|
for(var i=0; i<keys.length; i++){
|
||||||
|
obj[keys[i]] = replaceFunctionsWithValues(obj[keys[i]], null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(var i=0; i<keys.length; i++){
|
||||||
|
if(typeof info.nested[keys[i]] == 'object' && info.nested[keys[i]].type !== jsPsych.plugins.parameterType.FUNCTION){
|
||||||
|
obj[keys[i]] = replaceFunctionsWithValues(obj[keys[i]], info.nested[keys[i]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(typeof obj === 'function'){
|
||||||
// reset so jsPsych.timelineVariable() is no longer immediately executed
|
return obj();
|
||||||
jsPsych.internal.call_immediate = false;
|
}
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDefaultValues(trial){
|
function setDefaultValues(trial){
|
||||||
|
@ -18,7 +18,7 @@ describe('The css_classes parameter for trials', function(){
|
|||||||
jsPsych.init({timeline:[trial]});
|
jsPsych.init({timeline:[trial]});
|
||||||
|
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Gracefully handles single class when not in array', function(){
|
test('Gracefully handles single class when not in array', function(){
|
||||||
@ -31,7 +31,7 @@ describe('The css_classes parameter for trials', function(){
|
|||||||
jsPsych.init({timeline:[trial]});
|
jsPsych.init({timeline:[trial]});
|
||||||
|
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Removes the added classes at the end of the trial', function(){
|
test('Removes the added classes at the end of the trial', function(){
|
||||||
@ -44,7 +44,7 @@ describe('The css_classes parameter for trials', function(){
|
|||||||
jsPsych.init({timeline:[trial]});
|
jsPsych.init({timeline:[trial]});
|
||||||
|
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -61,7 +61,7 @@ describe('The css_classes parameter for trials', function(){
|
|||||||
jsPsych.init({timeline:[tm]});
|
jsPsych.init({timeline:[tm]});
|
||||||
|
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -78,7 +78,7 @@ describe('The css_classes parameter for trials', function(){
|
|||||||
jsPsych.init({timeline:[trial]});
|
jsPsych.init({timeline:[trial]});
|
||||||
|
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -100,7 +100,7 @@ describe('The css_classes parameter for trials', function(){
|
|||||||
jsPsych.init({timeline:[t]});
|
jsPsych.init({timeline:[t]});
|
||||||
|
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(true);
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
expect(jsPsych.getDisplayElement().classList.contains('foo')).toBe(false);
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -393,11 +393,11 @@ describe('on_timeline_finish', function(){
|
|||||||
|
|
||||||
jsPsych.init({timeline: [mini_timeline]});
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_finish_fn).not.toHaveBeenCalled();
|
expect(on_finish_fn).not.toHaveBeenCalled();
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_finish_fn).not.toHaveBeenCalled();
|
expect(on_finish_fn).not.toHaveBeenCalled();
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_finish_fn).toHaveBeenCalled();
|
expect(on_finish_fn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -423,8 +423,8 @@ describe('on_timeline_finish', function(){
|
|||||||
|
|
||||||
jsPsych.init({timeline: [mini_timeline]});
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_finish_fn.mock.calls.length).toBe(1);
|
expect(on_finish_fn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -446,8 +446,8 @@ describe('on_timeline_finish', function(){
|
|||||||
|
|
||||||
jsPsych.init({timeline: [mini_timeline]});
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_finish_fn.mock.calls.length).toBe(2);
|
expect(on_finish_fn.mock.calls.length).toBe(2);
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -479,9 +479,9 @@ describe('on_timeline_start', function(){
|
|||||||
jsPsych.init({timeline: [mini_timeline]});
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
expect(on_start_fn).toHaveBeenCalled();
|
expect(on_start_fn).toHaveBeenCalled();
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_start_fn.mock.calls.length).toBe(1);
|
expect(on_start_fn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -509,8 +509,8 @@ describe('on_timeline_start', function(){
|
|||||||
jsPsych.init({timeline: [mini_timeline]});
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
expect(on_start_fn).toHaveBeenCalled();
|
expect(on_start_fn).toHaveBeenCalled();
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_start_fn.mock.calls.length).toBe(1);
|
expect(on_start_fn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -533,8 +533,8 @@ describe('on_timeline_start', function(){
|
|||||||
jsPsych.init({timeline: [mini_timeline]});
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
expect(on_start_fn).toHaveBeenCalled();
|
expect(on_start_fn).toHaveBeenCalled();
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(on_start_fn.mock.calls.length).toBe(2);
|
expect(on_start_fn.mock.calls.length).toBe(2);
|
||||||
|
|
||||||
})
|
})
|
||||||
|
210
tests/jsPsych/functions-as-parameters.test.js
Normal file
210
tests/jsPsych/functions-as-parameters.test.js
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
const root = '../../';
|
||||||
|
const utils = require('../testing-utils.js');
|
||||||
|
|
||||||
|
beforeEach(function(){
|
||||||
|
require(root + 'jspsych.js');
|
||||||
|
require(root + 'plugins/jspsych-html-keyboard-response.js');
|
||||||
|
require(root + 'plugins/jspsych-survey-text.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('standard use of function as parameter', function(){
|
||||||
|
test('function value is used as parameter', function(){
|
||||||
|
var trial = {
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: function(){
|
||||||
|
return 'foo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
|
||||||
|
utils.pressKey('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('function evaluates at runtime', function(){
|
||||||
|
var x = 'foo';
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: function(){
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x = 'bar';
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
|
||||||
|
utils.pressKey('a');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parameters can be protected from early evaluation using jsPsych.plugins.parameterType.FUNCTION', function(){
|
||||||
|
require(root + 'plugins/jspsych-cloze.js');
|
||||||
|
|
||||||
|
var mock = jest.fn();
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'cloze',
|
||||||
|
text: '%foo%',
|
||||||
|
check_answers: true,
|
||||||
|
mistake_fn: mock
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [trial]});
|
||||||
|
|
||||||
|
expect(mock).not.toHaveBeenCalled();
|
||||||
|
utils.clickTarget(document.querySelector('#finish_cloze_button'));
|
||||||
|
expect(mock).toHaveBeenCalledTimes(1);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('data as function', function(){
|
||||||
|
test('entire data object can be function', function(){
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo',
|
||||||
|
data: function(){
|
||||||
|
return {x:1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
})
|
||||||
|
|
||||||
|
utils.pressKey('a');
|
||||||
|
expect(jsPsych.data.get().values()[0].x).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('single parameter of data object can be function', function(){
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo',
|
||||||
|
data: {
|
||||||
|
x: function() { return 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
})
|
||||||
|
|
||||||
|
utils.pressKey('a');
|
||||||
|
expect(jsPsych.data.get().values()[0].x).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('nested parameters as functions', function(){
|
||||||
|
|
||||||
|
test('entire parameter can be a function', function(){
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'survey-text',
|
||||||
|
questions: function(){
|
||||||
|
return [{prompt: "How old are you?"}, {prompt: "Where were you born?"}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().querySelectorAll('p.jspsych-survey-text').length).toBe(2);
|
||||||
|
|
||||||
|
utils.clickTarget(document.querySelector('#jspsych-survey-text-next'));
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toBe('');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested parameter can be a function', function(){
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'survey-text',
|
||||||
|
questions: [{prompt: function(){ return "foo"; }}, {prompt: "bar"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(document.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('bar');
|
||||||
|
utils.clickTarget(document.querySelector('#jspsych-survey-text-next'));
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toBe('');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('multiple nested parameters can be functions', function(){
|
||||||
|
require(root + 'plugins/jspsych-survey-multi-choice.js');
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'survey-multi-choice',
|
||||||
|
questions: [
|
||||||
|
{prompt: function(){ return "foo"; }, options: function() { return ['buzz','fizz']; }},
|
||||||
|
{prompt: "bar", options: function() { return ['one','two']; }}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
utils.clickTarget(document.querySelector('#jspsych-survey-multi-choice-next'));
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toBe('');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested parameters can be protected from early evaluation using jsPsych.plugins.parameterType.FUNCTION', function(){
|
||||||
|
// currently no plugins that use this feature (Jan. 2021), so here's a simple placeholder plugin.
|
||||||
|
jsPsych.plugins['fn-test'] = {};
|
||||||
|
jsPsych.plugins['fn-test'].info = {
|
||||||
|
parameters: {
|
||||||
|
foo: {
|
||||||
|
type: jsPsych.plugins.parameterType.COMPLEX,
|
||||||
|
default: null,
|
||||||
|
nested: {
|
||||||
|
not_protected: {
|
||||||
|
type: jsPsych.plugins.parameterType.STRING,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
protected: {
|
||||||
|
type: jsPsych.plugins.parameterType.FUNCTION,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsPsych.plugins['fn-test'].trial = function(display_element, trial){
|
||||||
|
jsPsych.finishTrial({
|
||||||
|
not_protected: trial.foo[0].not_protected,
|
||||||
|
protected: trial.foo[0].protected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'fn-test',
|
||||||
|
foo: [{
|
||||||
|
not_protected: function(){ return 'x';},
|
||||||
|
protected: function() { return 'y';}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [trial]});
|
||||||
|
|
||||||
|
var data = jsPsych.data.get().values()[0];
|
||||||
|
expect(data.not_protected).toBe('x');
|
||||||
|
expect(data.protected).not.toBe('y');
|
||||||
|
expect(data.protected()).toBe('y');
|
||||||
|
})
|
||||||
|
})
|
@ -273,7 +273,7 @@ describe('timeline variables are correctly evaluated', function(){
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ describe('timeline variables are correctly evaluated', function(){
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(x).toBe('foo');
|
expect(x).toBe('foo');
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -333,7 +333,7 @@ describe('timeline variables are correctly evaluated', function(){
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(x).toBe('foo');
|
expect(x).toBe('foo');
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ describe('timeline variables are correctly evaluated', function(){
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(jsPsych.data.get().values()[0].x).toBe('foo');
|
expect(jsPsych.data.get().values()[0].x).toBe('foo');
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -389,7 +389,7 @@ describe('timeline variables are correctly evaluated', function(){
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(x).toBe('foo');
|
expect(x).toBe('foo');
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -418,7 +418,7 @@ describe('timeline variables are correctly evaluated', function(){
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
expect(x).toBe('foo');
|
expect(x).toBe('foo');
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -443,8 +443,8 @@ describe('jsPsych.allTimelineVariables()', function(){
|
|||||||
|
|
||||||
jsPsych.init({timeline: [t]});
|
jsPsych.init({timeline: [t]});
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
var data = jsPsych.data.get().values();
|
var data = jsPsych.data.get().values();
|
||||||
expect(data[0].a).toBe(1);
|
expect(data[0].a).toBe(1);
|
||||||
@ -479,10 +479,10 @@ describe('jsPsych.allTimelineVariables()', function(){
|
|||||||
|
|
||||||
jsPsych.init({timeline: [t2]});
|
jsPsych.init({timeline: [t2]});
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
|
|
||||||
var data = jsPsych.data.get().values();
|
var data = jsPsych.data.get().values();
|
||||||
@ -523,8 +523,8 @@ describe('jsPsych.allTimelineVariables()', function(){
|
|||||||
|
|
||||||
jsPsych.init({timeline: [t]});
|
jsPsych.init({timeline: [t]});
|
||||||
|
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
expect(a).toBe(1);
|
expect(a).toBe(1);
|
||||||
expect(b).toBe(2);
|
expect(b).toBe(2);
|
||||||
|
@ -186,12 +186,12 @@ describe('loop function', function(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
// first trial
|
// first trial
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
expect(count).toBe(0);
|
expect(count).toBe(0);
|
||||||
|
|
||||||
// second trial
|
// second trial
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
expect(count).toBe(1);
|
expect(count).toBe(1);
|
||||||
})
|
})
|
||||||
@ -321,12 +321,12 @@ describe('conditional function', function(){
|
|||||||
expect(conditional_count).toBe(1);
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
// first trial
|
// first trial
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
expect(conditional_count).toBe(1);
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
// second trial
|
// second trial
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
expect(conditional_count).toBe(1);
|
expect(conditional_count).toBe(1);
|
||||||
})
|
})
|
||||||
@ -356,12 +356,12 @@ describe('conditional function', function(){
|
|||||||
expect(conditional_count).toBe(1);
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
// first trial
|
// first trial
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
expect(conditional_count).toBe(1);
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
// second trial
|
// second trial
|
||||||
utils.pressKey(32);
|
utils.pressKey('a');
|
||||||
|
|
||||||
expect(conditional_count).toBe(1);
|
expect(conditional_count).toBe(1);
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user