Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to plugin-self-paced-reading #79

Merged
merged 37 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4109ea5
interim
jkhartshorne Aug 21, 2023
8905d93
interim commit.
jkhartshorne Aug 21, 2023
605b976
Should now use jsPsych.finishTrial correctly. And tests have been mod…
jkhartshorne Aug 21, 2023
34db38f
modify trial_data to be object instead of array
jessestorbeck Sep 26, 2023
8e0b1c7
experimenting with word_number index
jessestorbeck Sep 26, 2023
f54f0db
add stimulus property to data object
jessestorbeck Sep 27, 2023
a28006d
testing
jessestorbeck Sep 27, 2023
4869631
fixing word_number bug, reorganizing
jessestorbeck Sep 28, 2023
56209d2
comments
jessestorbeck Sep 28, 2023
bcb8782
update tests
jessestorbeck Sep 28, 2023
fde0e77
add total trial rts
jessestorbeck Sep 28, 2023
3aebdf3
docs updates
jessestorbeck Sep 28, 2023
c05291a
set default iwi back to 0
jessestorbeck Sep 28, 2023
4f9fcbe
docs adjustments
jessestorbeck Sep 28, 2023
7581e98
bump version number
jessestorbeck Sep 28, 2023
19e03f3
didn't use getData properly before
jessestorbeck Sep 28, 2023
2d0e891
testing tests
jessestorbeck Sep 29, 2023
020a83c
missing ()
jessestorbeck Sep 29, 2023
4e60859
missing () again
jessestorbeck Sep 29, 2023
bde8144
add tests for RTs (with IWI)
jessestorbeck Sep 29, 2023
57b0df0
removed extra keypress from tests
jessestorbeck Sep 29, 2023
359b3d6
seems like extra keypress is required?
jessestorbeck Sep 29, 2023
b16b74d
fix typo
jessestorbeck Sep 29, 2023
df710eb
fixed extra keypress
jessestorbeck Oct 2, 2023
5fc5d64
add extra time after final keypress
jessestorbeck Oct 2, 2023
46b3cfd
changed to major version bump
jessestorbeck Oct 2, 2023
7f71741
back to 1.0.0 - changeset will bump automatically
jessestorbeck Oct 2, 2023
599a090
changeset added
jessestorbeck Oct 2, 2023
591240c
change version number in readme
jessestorbeck Oct 2, 2023
cf7d800
edit changeset summary
jessestorbeck Oct 2, 2023
a5ed94b
remove _spr data prefix
jessestorbeck Oct 2, 2023
dd8efd4
singularize data property names
jessestorbeck Oct 4, 2023
cbb4ce5
remove save_sentence parameter
jessestorbeck Oct 4, 2023
19f9639
set version number back (will auto-increment)
jessestorbeck Oct 4, 2023
7362a9a
.last(1) unnecessary: only 1 trial now
jessestorbeck Oct 4, 2023
86c353e
update author information
jessestorbeck Oct 5, 2023
465f58c
remove yarn.lock
jessestorbeck Oct 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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