mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-12 16:48:12 +00:00
Merge pull request #1494 from jspsych/feature-timeline-events
merge timeline events into all timeline vars branch
This commit is contained in:
commit
0272250bb4
60
jspsych.js
60
jspsych.js
@ -505,10 +505,11 @@ window.jsPsych = (function() {
|
|||||||
// if node has not started yet (progress.current_location == -1),
|
// if node has not started yet (progress.current_location == -1),
|
||||||
// then try to start the node.
|
// then try to start the node.
|
||||||
if (progress.current_location == -1) {
|
if (progress.current_location == -1) {
|
||||||
// check for conditonal function on nodes with timelines
|
// check for on_timeline_start and conditonal function on nodes with timelines
|
||||||
if (typeof timeline_parameters != 'undefined') {
|
if (typeof timeline_parameters != 'undefined') {
|
||||||
if (typeof timeline_parameters.conditional_function !== 'undefined') {
|
// only run the conditional function if this is the first repetition of the timeline when
|
||||||
jsPsych.internal.call_immediate = true;
|
// repetitions > 1, and only when on the first variable set
|
||||||
|
if (typeof timeline_parameters.conditional_function !== 'undefined' && progress.current_repetition==0 && progress.current_variable_set == 0) {
|
||||||
var conditional_result = timeline_parameters.conditional_function();
|
var conditional_result = timeline_parameters.conditional_function();
|
||||||
jsPsych.internal.call_immediate = false;
|
jsPsych.internal.call_immediate = false;
|
||||||
// if the conditional_function() returns false, then the timeline
|
// if the conditional_function() returns false, then the timeline
|
||||||
@ -517,17 +518,23 @@ window.jsPsych = (function() {
|
|||||||
progress.done = true;
|
progress.done = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// if the conditonal_function() returns true, then the node can start
|
// // if the conditonal_function() returns true, then the node can start
|
||||||
else {
|
// else {
|
||||||
progress.current_location = 0;
|
// progress.current_location = 0;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
// if we reach this point then the node has its own timeline and will start
|
||||||
|
// so we need to check if there is an on_timeline_start function
|
||||||
|
if (typeof timeline_parameters.on_timeline_start !== 'undefined'){
|
||||||
|
timeline_parameters.on_timeline_start();
|
||||||
}
|
}
|
||||||
// if there is no conditional_function, then the node can start
|
// // if there is no conditional_function, then the node can start
|
||||||
else {
|
// else {
|
||||||
progress.current_location = 0;
|
// progress.current_location = 0;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
// if we reach this point, then either the node doesn't have a timeline of the
|
||||||
// if the node does not have a timeline, then it can start
|
// conditional function returned true and it can start
|
||||||
progress.current_location = 0;
|
progress.current_location = 0;
|
||||||
// call advance again on this node now that it is pointing to a new location
|
// call advance again on this node now that it is pointing to a new location
|
||||||
return this.advance();
|
return this.advance();
|
||||||
@ -552,6 +559,7 @@ window.jsPsych = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we've reached the end of the timeline (which, if the code is here, we have)
|
// if we've reached the end of the timeline (which, if the code is here, we have)
|
||||||
|
|
||||||
// there are a few steps to see what to do next...
|
// there are a few steps to see what to do next...
|
||||||
|
|
||||||
// first, check the timeline_variables to see if we need to loop through again
|
// first, check the timeline_variables to see if we need to loop through again
|
||||||
@ -566,11 +574,23 @@ window.jsPsych = (function() {
|
|||||||
// if we're all done with the timeline_variables, then check to see if there are more repetitions
|
// if we're all done with the timeline_variables, then check to see if there are more repetitions
|
||||||
else if (progress.current_repetition < timeline_parameters.repetitions - 1) {
|
else if (progress.current_repetition < timeline_parameters.repetitions - 1) {
|
||||||
this.nextRepetiton();
|
this.nextRepetiton();
|
||||||
|
// check to see if there is an on_timeline_finish function
|
||||||
|
if (typeof timeline_parameters.on_timeline_finish !== 'undefined'){
|
||||||
|
timeline_parameters.on_timeline_finish();
|
||||||
|
}
|
||||||
return this.advance();
|
return this.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if we're all done with the repetitions...
|
||||||
|
else {
|
||||||
|
// check to see if there is an on_timeline_finish function
|
||||||
|
if (typeof timeline_parameters.on_timeline_finish !== 'undefined'){
|
||||||
|
timeline_parameters.on_timeline_finish();
|
||||||
|
}
|
||||||
|
|
||||||
// if we're all done with the repetitions, check if there is a loop function.
|
// if we're all done with the repetitions, check if there is a loop function.
|
||||||
else if (typeof timeline_parameters.loop_function !== 'undefined') {
|
if (typeof timeline_parameters.loop_function !== 'undefined') {
|
||||||
jsPsych.internal.call_immediate = true;
|
jsPsych.internal.call_immediate = true;
|
||||||
if (timeline_parameters.loop_function(this.generatedData())) {
|
if (timeline_parameters.loop_function(this.generatedData())) {
|
||||||
this.reset();
|
this.reset();
|
||||||
@ -583,12 +603,12 @@ window.jsPsych = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no more loops on this timeline, we're done!
|
|
||||||
else {
|
|
||||||
progress.done = true;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no more loops on this timeline, we're done!
|
||||||
|
progress.done = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,12 +838,16 @@ window.jsPsych = (function() {
|
|||||||
sample: parameters.sample,
|
sample: parameters.sample,
|
||||||
randomize_order: typeof parameters.randomize_order == 'undefined' ? false : parameters.randomize_order,
|
randomize_order: typeof parameters.randomize_order == 'undefined' ? false : parameters.randomize_order,
|
||||||
repetitions: typeof parameters.repetitions == 'undefined' ? 1 : parameters.repetitions,
|
repetitions: typeof parameters.repetitions == 'undefined' ? 1 : parameters.repetitions,
|
||||||
timeline_variables: typeof parameters.timeline_variables == 'undefined' ? [{}] : parameters.timeline_variables
|
timeline_variables: typeof parameters.timeline_variables == 'undefined' ? [{}] : parameters.timeline_variables,
|
||||||
|
on_timeline_finish: parameters.on_timeline_finish,
|
||||||
|
on_timeline_start: parameters.on_timeline_start,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.setTimelineVariablesOrder();
|
self.setTimelineVariablesOrder();
|
||||||
|
|
||||||
// extract all of the node level data and parameters
|
// extract all of the node level data and parameters
|
||||||
|
// but remove all of the timeline-level specific information
|
||||||
|
// since this will be used to copy things down hierarchically
|
||||||
var node_data = Object.assign({}, parameters);
|
var node_data = Object.assign({}, parameters);
|
||||||
delete node_data.timeline;
|
delete node_data.timeline;
|
||||||
delete node_data.conditional_function;
|
delete node_data.conditional_function;
|
||||||
@ -832,6 +856,8 @@ window.jsPsych = (function() {
|
|||||||
delete node_data.repetitions;
|
delete node_data.repetitions;
|
||||||
delete node_data.timeline_variables;
|
delete node_data.timeline_variables;
|
||||||
delete node_data.sample;
|
delete node_data.sample;
|
||||||
|
delete node_data.on_timeline_start;
|
||||||
|
delete node_data.on_timeline_finish;
|
||||||
node_trial_data = node_data; // store for later...
|
node_trial_data = node_data; // store for later...
|
||||||
|
|
||||||
// create a TimelineNode for each element in the timeline
|
// create a TimelineNode for each element in the timeline
|
||||||
|
@ -367,3 +367,175 @@ describe('on_trial_start', function(){
|
|||||||
utils.pressKey('a');
|
utils.pressKey('a');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('on_timeline_finish', function(){
|
||||||
|
test('should fire once when timeline is complete', function(){
|
||||||
|
|
||||||
|
var on_finish_fn = jest.fn();
|
||||||
|
|
||||||
|
var mini_timeline = {
|
||||||
|
timeline: [
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on_timeline_finish: on_finish_fn
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_finish_fn).not.toHaveBeenCalled();
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_finish_fn).not.toHaveBeenCalled();
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_finish_fn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fire once even with timeline variables', function(){
|
||||||
|
|
||||||
|
var on_finish_fn = jest.fn();
|
||||||
|
|
||||||
|
var tvs = [{
|
||||||
|
x: 1,
|
||||||
|
x: 2,
|
||||||
|
}]
|
||||||
|
|
||||||
|
var mini_timeline = {
|
||||||
|
timeline: [
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on_timeline_finish: on_finish_fn,
|
||||||
|
timeline_variables: tvs
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
|
utils.pressKey(32);
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_finish_fn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should fire on every repetition', function(){
|
||||||
|
|
||||||
|
var on_finish_fn = jest.fn();
|
||||||
|
|
||||||
|
var mini_timeline = {
|
||||||
|
timeline: [
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on_timeline_finish: on_finish_fn,
|
||||||
|
repetitions: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
|
utils.pressKey(32);
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_finish_fn.mock.calls.length).toBe(2);
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('on_timeline_start', function(){
|
||||||
|
test('should fire once when timeline starts', function(){
|
||||||
|
|
||||||
|
var on_start_fn = jest.fn();
|
||||||
|
|
||||||
|
var mini_timeline = {
|
||||||
|
timeline: [
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on_timeline_start: on_start_fn
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
|
expect(on_start_fn).toHaveBeenCalled();
|
||||||
|
utils.pressKey(32);
|
||||||
|
utils.pressKey(32);
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_start_fn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should fire once even with timeline variables', function(){
|
||||||
|
|
||||||
|
var on_start_fn = jest.fn();
|
||||||
|
|
||||||
|
var tvs = [{
|
||||||
|
x: 1,
|
||||||
|
x: 2,
|
||||||
|
}]
|
||||||
|
|
||||||
|
var mini_timeline = {
|
||||||
|
timeline: [
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on_timeline_start: on_start_fn,
|
||||||
|
timeline_variables: tvs
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
|
expect(on_start_fn).toHaveBeenCalled();
|
||||||
|
utils.pressKey(32);
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_start_fn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should fire on every repetition', function(){
|
||||||
|
|
||||||
|
var on_start_fn = jest.fn();
|
||||||
|
|
||||||
|
var mini_timeline = {
|
||||||
|
timeline: [
|
||||||
|
{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on_timeline_start: on_start_fn,
|
||||||
|
repetitions: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({timeline: [mini_timeline]});
|
||||||
|
|
||||||
|
expect(on_start_fn).toHaveBeenCalled();
|
||||||
|
utils.pressKey(32);
|
||||||
|
utils.pressKey(32);
|
||||||
|
expect(on_start_fn.mock.calls.length).toBe(2);
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
@ -166,6 +166,36 @@ describe('loop function', function(){
|
|||||||
utils.pressKey('a');
|
utils.pressKey('a');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('only runs once when timeline variables are used', function(){
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
timeline: [{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}],
|
||||||
|
timeline_variables:[{a:1},{a:2}],
|
||||||
|
loop_function: function(){
|
||||||
|
count++;
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
// first trial
|
||||||
|
utils.pressKey(32);
|
||||||
|
|
||||||
|
expect(count).toBe(0);
|
||||||
|
|
||||||
|
// second trial
|
||||||
|
utils.pressKey(32);
|
||||||
|
|
||||||
|
expect(count).toBe(1);
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('conditional function', function(){
|
describe('conditional function', function(){
|
||||||
@ -269,6 +299,73 @@ describe('conditional function', function(){
|
|||||||
expect(conditional_count).toBe(2);
|
expect(conditional_count).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('executes only once even when repetitions is > 1', function(){
|
||||||
|
var conditional_count = 0;
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
timeline: [{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}],
|
||||||
|
repetitions: 2,
|
||||||
|
conditional_function: function(){
|
||||||
|
conditional_count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
|
// first trial
|
||||||
|
utils.pressKey(32);
|
||||||
|
|
||||||
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
|
// second trial
|
||||||
|
utils.pressKey(32);
|
||||||
|
|
||||||
|
expect(conditional_count).toBe(1);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('executes only once when timeline variables are used', function(){
|
||||||
|
var conditional_count = 0;
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
timeline: [{
|
||||||
|
type: 'html-keyboard-response',
|
||||||
|
stimulus: 'foo'
|
||||||
|
}],
|
||||||
|
timeline_variables: [
|
||||||
|
{a:1},
|
||||||
|
{a:2}
|
||||||
|
],
|
||||||
|
conditional_function: function(){
|
||||||
|
conditional_count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
|
// first trial
|
||||||
|
utils.pressKey(32);
|
||||||
|
|
||||||
|
expect(conditional_count).toBe(1);
|
||||||
|
|
||||||
|
// second trial
|
||||||
|
utils.pressKey(32);
|
||||||
|
|
||||||
|
expect(conditional_count).toBe(1);
|
||||||
|
})
|
||||||
|
|
||||||
test('timeline variables from nested timelines are available', function(){
|
test('timeline variables from nested timelines are available', function(){
|
||||||
var trial = {
|
var trial = {
|
||||||
type: 'html-keyboard-response',
|
type: 'html-keyboard-response',
|
||||||
|
Loading…
Reference in New Issue
Block a user