Skip to content

Commit

Permalink
Merge pull request #79 from jessestorbeck/SRD-data
Browse files Browse the repository at this point in the history
Updates to plugin-self-paced-reading
  • Loading branch information
jodeleeuw authored Oct 5, 2023
2 parents f3b64ff + 465f58c commit 9afb139
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 103 deletions.
7 changes: 7 additions & 0 deletions .changeset/red-drinks-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@jspsych-contrib/plugin-self-paced-reading": major
---

- Calls to jsPsych.data.write() are removed; jsPsych.finishTrial() now gets an object with words and reading times stored as arrays. Thus a trial now generates a single data object instead of x, where x is the number of words in the sentence.
- A bug when inter_word_interval > 0 is corrected.
- Tests are added related to reading times.
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,46 @@ jsPsych plugin for self-paced reading experiments.

## Parameters

In addition to the [parameters available in all plugins](https://www.jspsych.org/overview/plugins#parameters-available-in-all-plugins), this plugin accepts the following parameters. Parameters with a default value of *undefined* must be specified. Other parameters can be left unspecified if the default value is acceptable.

| Parameter | Type | Default Value | Description |
| ---------------------- | -------- | -----------------| ------------------------------------------------------- |
| sentence | string | undefined | The sentence to be presented |
| font_family | string | "monospace" | Font to use (NB. requires monospaced font type) |
| font_size | string | "24px" | Font size |
| font_weight | string | "normal" | Font weight |
| font_colour | string | "black" | Font colour |
| mask_type | number | 1 | The type of mask (1 vs. 2 vs. 3) |
| mask_character | string | "_" | The character used as the mask |
| mask_on_word | bool | false | Display the mask together with the word |
| mask_gap_character | string | " " | Character to display between the masked words |
| mask_offset | number | 0 | Mask offset in y direction |
| mask_weight | string | "normal" | Mask weight |
| mask_colour | string | "black" | Mask colour |
| line_height | number | 80 | Line height for multiline text |
| canvas_colour | string | "white" | Canvas colour |
| canvas_size | number[] | [1280 960] | Canvas size |
| canvas_border | string | "0px solid black | Canvas border |
| canvas_clear_border | bool | true | Clear screen following final word in sentence |
| translate_origin | bool | false | Translate coordinates to [0,0] at centre |
| choices | string[] | " " | Key to press |
| xy_position | number[] | [0, 0] | X and Y position |
| x_align | string | "center" | X alignment |
| y_align | string | "top" | Y alignment |
| inter_word_interval | number | 0 | Interval (in ms) between succesive words |
| save_sentence | bool | true | Keep sentence in results file |
In addition to the [parameters available in all plugins](https://www.jspsych.org/overview/plugins#parameters-available-in-all-plugins), this plugin accepts the following parameters. Parameters with a default value of _undefined_ must be specified. Other parameters can be left unspecified if the default value is acceptable.

| Parameter | Type | Default Value | Description |
| ------------------- | -------- | ---------------- | ----------------------------------------------- |
| sentence | string | undefined | The sentence to be presented |
| font_family | string | "monospace" | Font to use (NB. requires monospaced font type) |
| font_size | string | "24px" | Font size |
| font_weight | string | "normal" | Font weight |
| font_colour | string | "black" | Font colour |
| mask_type | number | 1 | The type of mask (1 vs. 2 vs. 3) |
| mask_character | string | "\_" | The character used as the mask |
| mask_on_word | bool | false | Display the mask together with the word |
| mask_gap_character | string | " " | Character to display between the masked words |
| mask_offset | number | 0 | Mask offset in y direction |
| mask_weight | string | "normal" | Mask weight |
| mask_colour | string | "black" | Mask colour |
| line_height | number | 80 | Line height for multiline text |
| canvas_colour | string | "white" | Canvas colour |
| canvas_size | number[] | [1280 960] | Canvas size |
| canvas_border | string | "0px solid black | Canvas border |
| canvas_clear_border | bool | true | Clear screen following final word in sentence |
| translate_origin | bool | false | Translate coordinates to [0,0] at centre |
| choices | string[] | " " | Key to press |
| xy_position | number[] | [0, 0] | X and Y position |
| x_align | string | "center" | X alignment |
| y_align | string | "top" | Y alignment |
| inter_word_interval | number | 0 | Delay (in ms) between keypress and next word |

## Data Generated

In addition to the [default data collected by all plugins](https://www.jspsych.org/overview/plugins#data-collected-by-all-plugins), this plugin collects the following data for each trial.

| Name | Type | Value |
| -------- | ----------- | ---------------------------------------- |
| rt_sentence | number | Running total time (ms) |
| rt_word | number | Individual word time (ms) |
| word | string | Individual word |
| word_number | number | Individual word number within sentence |
| sentence | string | Sentence item |
| Name | Type | Value |
| -------- | -------- | ---------------------------------------------------------- |
| word | string[] | Array of words in the sentence |
| rt | number[] | Array of reading times for each word (ms) |
| rt_total | number[] | Array of reading times for sentence through each word (ms) |
| sentence | string | Item sentence |

## Example
## Example

```javascript
const sentence = [ `The quick brown fox jumps over the lazy dog.`];
Expand Down
5 changes: 3 additions & 2 deletions packages/plugin-self-paced-reading/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ jsPsych v7.0.0

See [documentation](docs/jspsych-self-paced-reading.md)


## Author / Citation

igmmgi
[igmmgi](https://github.com/igmmgi)
[jessestorbeck](https://github.com/jessestorbeck)
[jkhartshorne](https://github.com/jkhartshorne)
156 changes: 121 additions & 35 deletions packages/plugin-self-paced-reading/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SelfPacedReadingPlugin from ".";
jest.useFakeTimers();

describe("self-paced-reading plugin", () => {
test("Click through sentence: Mask 1", async () => {
test("Click through sentence A: Mask 1", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
Expand All @@ -14,19 +14,24 @@ describe("self-paced-reading plugin", () => {
},
]);

// number of key presses meeded to complete trial
for (let i = 0; i <= 10; i++) {
pressKey(" ");
// number of key presses needed to complete trial
for (let i = 0; i < 10; i++) {
jest.advanceTimersByTime(100);
pressKey(" ");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

expect(getData().last(1).values()[0].word).toBe("dog.");
expect(getData().last(1).values()[0].word_number).toBe(9);
expect(getData().values()[0].word[9]).toBe("dog.");
expect(getData().values()[0].word.length).toBe(10);
for (let i = 0; i < 10; i++) {
expect(getData().select("rt").values[0][i]).toBe(100);
}
});

test("Click through sentence: Mask 1", async () => {
test("Click through sentence B: Mask 1", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
Expand All @@ -35,19 +40,24 @@ describe("self-paced-reading plugin", () => {
},
]);

// number of key presses meeded to complete trial
for (let i = 0; i <= 6; i++) {
pressKey("ArrowRight");
// number of key presses needed to complete trial
for (let i = 0; i < 6; i++) {
jest.advanceTimersByTime(100);
pressKey("ArrowRight");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

expect(getData().last(1).values()[0].word).toBe("five");
expect(getData().last(1).values()[0].word_number).toBe(5);
expect(getData().values()[0].word[5]).toBe("five");
expect(getData().values()[0].word.length).toBe(6);
for (let i = 0; i < 6; i++) {
expect(getData().select("rt").values[0][i]).toBe(100);
}
});

test("Click through sentence: Mask 2", async () => {
test("Click through sentence A: Mask 2", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
Expand All @@ -57,19 +67,24 @@ describe("self-paced-reading plugin", () => {
},
]);

// number of key presses meeded to complete trial
for (let i = 0; i <= 10; i++) {
pressKey(" ");
// number of key presses needed to complete trial
for (let i = 0; i < 10; i++) {
jest.advanceTimersByTime(100);
pressKey(" ");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

expect(getData().last(1).values()[0].word).toBe("dog.");
expect(getData().last(1).values()[0].word_number).toBe(9);
expect(getData().values()[0].word[9]).toBe("dog.");
expect(getData().values()[0].word.length).toBe(10);
for (let i = 0; i < 10; i++) {
expect(getData().select("rt").values[0][i]).toBe(100);
}
});

test("Click through sentence: Mask 2", async () => {
test("Click through sentence B: Mask 2", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
Expand All @@ -79,19 +94,24 @@ describe("self-paced-reading plugin", () => {
},
]);

// number of key presses meeded to complete trial
for (let i = 0; i <= 6; i++) {
pressKey("ArrowRight");
// number of key presses needed to complete trial
for (let i = 0; i < 6; i++) {
jest.advanceTimersByTime(100);
pressKey("ArrowRight");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

expect(getData().last(1).values()[0].word).toBe("five");
expect(getData().last(1).values()[0].word_number).toBe(5);
expect(getData().values()[0].word[5]).toBe("five");
expect(getData().values()[0].word.length).toBe(6);
for (let i = 0; i < 6; i++) {
expect(getData().select("rt").values[0][i]).toBe(100);
}
});

test("Click through sentence: Mask 3", async () => {
test("Click through sentence A: Mask 3", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
Expand All @@ -101,19 +121,24 @@ describe("self-paced-reading plugin", () => {
},
]);

// number of key presses meeded to complete trial
for (let i = 0; i <= 10; i++) {
pressKey(" ");
// number of key presses needed to complete trial
for (let i = 0; i < 10; i++) {
jest.advanceTimersByTime(100);
pressKey(" ");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

expect(getData().last(1).values()[0].word).toBe("dog.");
expect(getData().last(1).values()[0].word_number).toBe(9);
expect(getData().values()[0].word[9]).toBe("dog.");
expect(getData().values()[0].word.length).toBe(10);
for (let i = 0; i < 10; i++) {
expect(getData().select("rt").values[0][i]).toBe(100);
}
});

test("Click through sentence: Mask 3", async () => {
test("Click through sentence B: Mask 3", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
Expand All @@ -123,15 +148,76 @@ describe("self-paced-reading plugin", () => {
},
]);

// number of key presses meeded to complete trial
for (let i = 0; i <= 6; i++) {
// number of key presses needed to complete trial
for (let i = 0; i < 6; i++) {
jest.advanceTimersByTime(100);
pressKey("ArrowRight");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

expect(getData().values()[0].word[5]).toBe("five");
expect(getData().values()[0].word.length).toBe(6);
for (let i = 0; i < 6; i++) {
expect(getData().select("rt").values[0][i]).toBe(100);
}
});

test("Click through sentence A: Mask 1, with IWI", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
sentence: "The quick brown fox jumps over the lazy dog.",
choices: [" "],
inter_word_interval: 50,
},
]);

// number of key presses needed to complete trial
for (let i = 0; i < 10; i++) {
jest.advanceTimersByTime(100);
pressKey(" ");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

expect(getData().last(1).values()[0].word).toBe("five");
expect(getData().last(1).values()[0].word_number).toBe(5);
// First keypress should have rt of 100ms
expect(getData().select("rt").values[0][0]).toBe(100);
// Because of IWI, subsequent keypresses should have rt of 50ms
for (let i = 1; i < 10; i++) {
expect(getData().select("rt").values[0][i]).toBe(50);
}
});

test("Click through sentence B: Mask 1, with IWI", async () => {
const { expectFinished, getData } = await startTimeline([
{
type: SelfPacedReadingPlugin,
sentence: "One two three four five",
choices: ["ArrowRight"],
inter_word_interval: 50,
},
]);

// number of key presses needed to complete trial
for (let i = 0; i < 6; i++) {
jest.advanceTimersByTime(100);
pressKey("ArrowRight");
}
// This additional time seems necessary to get tests to pass
jest.advanceTimersByTime(100);

await expectFinished();

// First keypress should have rt of 100ms
expect(getData().select("rt").values[0][0]).toBe(100);
// Because of IWI, subsequent keypresses should have rt of 50ms
for (let i = 1; i < 6; i++) {
expect(getData().select("rt").values[0][i]).toBe(50);
}
});
});
Loading

0 comments on commit 9afb139

Please sign in to comment.