mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 11:10:54 +00:00
Merge branch 'main' into patch-inline-fonts
This commit is contained in:
commit
62b131efae
5
.changeset/happy-shirts-drum.md
Normal file
5
.changeset/happy-shirts-drum.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"jspsych": minor
|
||||
---
|
||||
|
||||
Added `filterColumns()` to the DataCollection class. This function lets users select a subset of the columns in the DataCollection. It is the opposite of the `ignore()` method.
|
5
.changeset/rotten-worms-float.md
Normal file
5
.changeset/rotten-worms-float.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"jspsych": minor
|
||||
---
|
||||
|
||||
Added `setSeed()` to `jsPsych.randomization` to allow for seeding the random number generator and generating predictable sequences of random numbers.
|
5
.changeset/sweet-cheetahs-grin.md
Normal file
5
.changeset/sweet-cheetahs-grin.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jspsych/config": minor
|
||||
---
|
||||
|
||||
Implement an `updateUnpkgLinks` Gulp task to update each unpkg link with a precise version number to the corresponding package's current version as defined in the package's `package.json`.
|
5
.changeset/yellow-ducks-poke.md
Normal file
5
.changeset/yellow-ducks-poke.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jspsych/config": patch
|
||||
---
|
||||
|
||||
Fix css path rewriting in `createCoreDistArchive` Gulp task when `link` tags do not end in `/>`
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
|
||||
- name: Download Turborepo cache
|
||||
uses: actions/cache@v2
|
||||
@ -35,9 +35,6 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-16-turbo-
|
||||
|
||||
- name: Build packages
|
||||
run: npm run build
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test -- --ci --maxWorkers=2
|
||||
env:
|
||||
@ -47,7 +44,8 @@ jobs:
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
publish: npm run release
|
||||
version: npm run changeset:version
|
||||
publish: npm run changeset:publish
|
||||
commit: "chore(release): version packages"
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
@ -15,7 +15,7 @@
|
||||
const trial = {
|
||||
type: jsPsychBrowserCheck,
|
||||
inclusion_function: (data) => {
|
||||
return ['chrome', 'firefox'].contains(data.browser);
|
||||
return ['chrome', 'firefox'].includes(data.browser);
|
||||
},
|
||||
exclusion_message: `<p>You must use Chrome or Firefox to complete this experiment.</p>`
|
||||
};
|
||||
|
@ -175,7 +175,7 @@ If you have tips based on your own experience please consider sharing them on ou
|
||||
<script src="https://unpkg.com/@jspsych/plugin-webgazer-init-camera@1.0.0"></script>
|
||||
<script src="https://unpkg.com/@jspsych/plugin-webgazer-calibrate@1.0.0"></script>
|
||||
<script src="https://unpkg.com/@jspsych/plugin-webgazer-validate@1.0.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/jspsych/jspsych@7.1.2/examples/js/webgazer/webgazer.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@jspsych@7.1.2/examples/js/webgazer/webgazer.js"></script>
|
||||
<script src="https://unpkg.com/@jspsych/extension-webgazer@1.0.0"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
@ -388,4 +388,4 @@ If you have tips based on your own experience please consider sharing them on ou
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Intergrating with Prolific
|
||||
# Integrating with Prolific
|
||||
|
||||
[Prolific](https://www.prolific.co/?ref=5JCXZPVU) is a participant recruitment service aimed at research. Integrating a jsPsych experiment with Prolific requires capturing the participant's ID and sending the participant to a completion URL at the end of the experiment.
|
||||
|
||||
|
@ -96,7 +96,7 @@ As with all simulated plugins, you can override the default (actual) data with f
|
||||
var trial = {
|
||||
type: jsPsychBrowserCheck,
|
||||
inclusion_function: (data) => {
|
||||
return ['chrome', 'firefox'].contains(data.browser);
|
||||
return ['chrome', 'firefox'].includes(data.browser);
|
||||
},
|
||||
exclusion_message: `<p>You must use Chrome or Firefox to complete this experiment.</p>`
|
||||
};
|
||||
@ -149,4 +149,4 @@ As with all simulated plugins, you can override the default (actual) data with f
|
||||
<iframe src="../../demos/jspsych-browser-check-demo4.html" width="90%;" height="500px;" frameBorder="0"></iframe>
|
||||
</div>
|
||||
|
||||
<a target="_blank" rel="noopener noreferrer" href="../../demos/jspsych-browser-check-demo4.html">Open demo in new tab</a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="../../demos/jspsych-browser-check-demo4.html">Open demo in new tab</a>
|
||||
|
@ -1,6 +1,6 @@
|
||||
# webgazer-calibrate
|
||||
|
||||
This plugin can be used to calibrate the [WebGazer extension](../extensions/webgazer). For a narrative description of eye tracking with jsPsych, see the [eye tracking overview](../overview/eye-tracking).
|
||||
This plugin can be used to calibrate the [WebGazer extension](../extensions/webgazer.md). For a narrative description of eye tracking with jsPsych, see the [eye tracking overview](../overview/eye-tracking.md).
|
||||
|
||||
## Parameters
|
||||
|
||||
@ -23,7 +23,7 @@ In addition to the [default data collected by all plugins](../overview/plugins.m
|
||||
Name | Type | Value
|
||||
-----|------|------
|
||||
|
||||
No data currently added by this plugin. Use the [webgazer-validate](jspsych-webgazer-validate) plugin to measure the precision and accuracy of calibration.
|
||||
No data currently added by this plugin. Use the [webgazer-validate](../webgazer-validate) plugin to measure the precision and accuracy of calibration.
|
||||
|
||||
## Simulation Mode
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# webgazer-init-camera
|
||||
|
||||
This plugin initializes the camera and helps the participant center their face in the camera view for using the the [WebGazer extension](../extensions/webgazer). For a narrative description of eye tracking with jsPsych, see the [eye tracking overview](../overview/eye-tracking).
|
||||
This plugin initializes the camera and helps the participant center their face in the camera view for using the the [WebGazer extension](../extensions/webgazer.md). For a narrative description of eye tracking with jsPsych, see the [eye tracking overview](../overview/eye-tracking.md).
|
||||
|
||||
## Parameters
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# webgazer-validate
|
||||
|
||||
This plugin can be used to measure the accuracy and precision of gaze predictions made by the [WebGazer extension](../extensions/webgazer). For a narrative description of eye tracking with jsPsych, see the [eye tracking overview](../overview/eye-tracking).
|
||||
This plugin can be used to measure the accuracy and precision of gaze predictions made by the [WebGazer extension](../extensions/webgazer.md). For a narrative description of eye tracking with jsPsych, see the [eye tracking overview](../overview/eye-tracking.md).
|
||||
|
||||
## Parameters
|
||||
|
||||
|
@ -364,6 +364,15 @@ The filter method returns a DataCollection object, so methods can be chained ont
|
||||
var block_1_correct = jsPsych.data.get().filter({block:1, correct:true}).count();
|
||||
```
|
||||
|
||||
#### .filterColumns()
|
||||
|
||||
Selects the set of columns listed in the array. This is the opposite of the `.ignore()` method.
|
||||
|
||||
```javascript
|
||||
// Get only the subject, rt, and condition entries for each trial.
|
||||
const subset_of_data = jsPsych.data.get().filterColumns(['subject', 'rt', 'condition'])
|
||||
```
|
||||
|
||||
#### .filterCustom()
|
||||
|
||||
This method is similar to the `.filter()` method, except that it accepts a function as the filter. The function is passed a single argument, containing the data for a trial. If the function returns `true` the trial is included in the returned DataCollection.
|
||||
|
@ -467,6 +467,51 @@ var sample = jsPsych.randomization.sampleWithoutReplacement(myArray, 2);
|
||||
|
||||
---
|
||||
|
||||
## jsPsych.randomization.setSeed
|
||||
|
||||
```javascript
|
||||
jsPsych.randomization.setSeed(seed)
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ----- | ------------------------------ |
|
||||
| seed | string | A seed for the random number generator |
|
||||
|
||||
### Return value
|
||||
|
||||
Returns the seed value.
|
||||
|
||||
### Description
|
||||
|
||||
This function will override the behavior of `Math.random()` to produce a seedable pseudo random number generator.
|
||||
It uses the [seedrandom package](https://www.npmjs.com/package/seedrandom).
|
||||
Note that calling `setSeed()` will change how `Math.random()` behaves for the entire document.
|
||||
If you have non-jsPsych components on the page that use `Math.random()` they will be affected.
|
||||
|
||||
Using `setSeed()` without passing in a seed will generate a random 32-bit seed.
|
||||
The seed value will be returned from the function call, allowing you to save it in the data for the experiment if needed.
|
||||
|
||||
### Examples
|
||||
|
||||
#### Use a random 32-bit seed and save to data
|
||||
|
||||
```javascript
|
||||
const seed = jsPsych.setSeed();
|
||||
jsPsych.data.addProperties({
|
||||
rng_seed: seed
|
||||
});
|
||||
```
|
||||
|
||||
#### Use your own seed
|
||||
|
||||
```javascript
|
||||
jsPsych.setSeed("jspsych");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## jsPsych.randomization.shuffle
|
||||
|
||||
```javascript
|
||||
|
@ -1 +1 @@
|
||||
export { createCoreDistArchive } from "@jspsych/config/gulp";
|
||||
export { createCoreDistArchive, updateUnpkgLinks } from "@jspsych/config/gulp";
|
||||
|
9515
package-lock.json
generated
9515
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@ -9,24 +9,27 @@
|
||||
"test:watch": "npm test -- --watch",
|
||||
"build": "turbo run build",
|
||||
"build:archive": "gulp createCoreDistArchive",
|
||||
"update-unpkg-links": "gulp updateUnpkgLinks",
|
||||
"prepare": "node -e 'process.exit(!process.env.CI)' || (husky install && npm run build)",
|
||||
"tsc": "turbo tsc",
|
||||
"changeset": "changeset",
|
||||
"release": "changeset publish"
|
||||
"changeset:version": "changeset version && npm install && npm run update-unpkg-links",
|
||||
"changeset:publish": "npm run build && changeset publish"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
},
|
||||
"packageManager": "npm@8.3.1",
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.4.1",
|
||||
"@changesets/cli": "^2.17.0",
|
||||
"husky": "^7.0.1",
|
||||
"@changesets/changelog-github": "^0.4.3",
|
||||
"@changesets/cli": "^2.21.1",
|
||||
"husky": "^7.0.4",
|
||||
"import-sort-style-module": "^6.0.0",
|
||||
"lint-staged": "^11.1.2",
|
||||
"prettier": "^2.3.2",
|
||||
"lint-staged": "^12.3.5",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-import-sort": "^0.0.7",
|
||||
"turbo": "^1.0.6"
|
||||
"turbo": "^1.1.6"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100
|
||||
@ -44,22 +47,5 @@
|
||||
"projects": [
|
||||
"<rootDir>/packages/*/jest.config.cjs"
|
||||
]
|
||||
},
|
||||
"turbo": {
|
||||
"baseBranch": "origin/main",
|
||||
"npmClient": "npm",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"dist/**"
|
||||
]
|
||||
},
|
||||
"tsc": {
|
||||
"outputs": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,20 @@ const { dest, src } = gulp;
|
||||
|
||||
const readJsonFile = (filename) => JSON.parse(readFileSync(filename, "utf8"));
|
||||
|
||||
const getAllPackages = () =>
|
||||
glob
|
||||
// Get an array of all package.json filenames
|
||||
.sync("packages/*/package.json")
|
||||
|
||||
// Map file names to package details
|
||||
.map((filename) => {
|
||||
const packageJson = readJsonFile(filename);
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
};
|
||||
});
|
||||
|
||||
const getVersionFileContents = () =>
|
||||
[
|
||||
"Included in this release:\n",
|
||||
@ -25,19 +39,7 @@ const getVersionFileContents = () =>
|
||||
"/";
|
||||
|
||||
return (
|
||||
glob
|
||||
// Get an array of all package.json filenames
|
||||
.sync("packages/*/package.json")
|
||||
|
||||
// Map file names to package details
|
||||
.map((filename) => {
|
||||
const packageJson = readJsonFile(filename);
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
};
|
||||
})
|
||||
|
||||
getAllPackages()
|
||||
// Filter packages that should not be listed
|
||||
.filter(({ name }) => !["@jspsych/config", "@jspsych/test-utils"].includes(name))
|
||||
|
||||
@ -85,15 +87,15 @@ export const createCoreDistArchive = () =>
|
||||
// Rewrite script source paths
|
||||
.pipe(
|
||||
replace(
|
||||
/<script src="(.*)\/packages\/(.*)\/dist\/index\.browser\.js"><\/script>/g,
|
||||
'<script src="$1/dist/$2.js"></script>'
|
||||
/<script src="(.*)\/packages\/(.*)\/dist\/index\.browser\.js"/g,
|
||||
'<script src="$1/dist/$2.js"'
|
||||
)
|
||||
)
|
||||
// Rewrite jspsych css source paths
|
||||
.pipe(
|
||||
replace(
|
||||
/<link rel="stylesheet" href="(.*)\/packages\/jspsych\/css\/(.*)" \/>/g,
|
||||
'<link rel="stylesheet" href="$1/dist/$2" />'
|
||||
/<link rel="stylesheet" href="(.*)\/packages\/jspsych\/css\/(.*)"/g,
|
||||
'<link rel="stylesheet" href="$1/dist/$2"'
|
||||
)
|
||||
),
|
||||
|
||||
@ -105,3 +107,23 @@ export const createCoreDistArchive = () =>
|
||||
)
|
||||
.pipe(zip("dist.zip"))
|
||||
.pipe(dest("."));
|
||||
|
||||
/**
|
||||
* Updates each unpkg link with a precise version number to the corresponding package's current
|
||||
* version as defined in the package's `package.json`. Only considers `.md` and `.html` files.
|
||||
*/
|
||||
export const updateUnpkgLinks = () => {
|
||||
const packageVersions = new Map(getAllPackages().map(({ name, version }) => [name, version]));
|
||||
|
||||
return src(["./**/*.{md,html}"])
|
||||
.pipe(
|
||||
replace(
|
||||
/"https:\/\/unpkg\.com\/(@?.*)@(\d+.\d+.\d+)(\/[^"]*)?"/g,
|
||||
(url, packageName, currentVersion, path) => {
|
||||
const latestVersion = packageVersions.get(packageName) ?? currentVersion;
|
||||
return `"https://unpkg.com/${packageName}@${latestVersion}${path ?? ""}"`;
|
||||
}
|
||||
)
|
||||
)
|
||||
.pipe(dest("./"));
|
||||
};
|
||||
|
@ -42,12 +42,14 @@
|
||||
"homepage": "https://www.jspsych.org",
|
||||
"dependencies": {
|
||||
"auto-bind": "^4.0.0",
|
||||
"random-words": "^1.1.1"
|
||||
"random-words": "^1.1.1",
|
||||
"seedrandom": "^3.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/open-sans": "4.5.3",
|
||||
"@jspsych/config": "^1.1.0",
|
||||
"@types/dom-mediacapture-record": "^1.0.11",
|
||||
"@types/seedrandom": "^3.0.1",
|
||||
"base64-inline-loader": "^2.0.1",
|
||||
"css-loader": "^6.6.0",
|
||||
"mini-css-extract-plugin": "^2.5.3",
|
||||
|
@ -134,6 +134,14 @@ export class DataCollection {
|
||||
return new DataCollection(this.trials.filter(fn));
|
||||
}
|
||||
|
||||
filterColumns(columns: Array<string>) {
|
||||
return new DataCollection(
|
||||
this.trials.map((trial) =>
|
||||
Object.fromEntries(columns.filter((key) => key in trial).map((key) => [key, trial[key]]))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
select(column) {
|
||||
const values = [];
|
||||
for (const trial of this.trials) {
|
||||
|
@ -1,4 +1,20 @@
|
||||
import rw from "random-words";
|
||||
import seedrandom from "seedrandom";
|
||||
|
||||
/**
|
||||
* Uses the `seedrandom` package to replace Math.random() with a seedable PRNG.
|
||||
*
|
||||
* @param seed An optional seed. If none is given, a random seed will be generated.
|
||||
* @returns The seed value.
|
||||
*/
|
||||
export function setSeed(seed?: string) {
|
||||
if (!seed) {
|
||||
const prng = seedrandom();
|
||||
seed = prng.int32().toString();
|
||||
}
|
||||
seedrandom(seed, { global: true });
|
||||
return seed;
|
||||
}
|
||||
|
||||
export function repeat(array, repetitions, unpack = false) {
|
||||
const arr_isArray = Array.isArray(array);
|
||||
|
@ -15,14 +15,14 @@ describe("DataCollection", () => {
|
||||
dataCollection = new DataCollection(data);
|
||||
});
|
||||
|
||||
test("#filter", () => {
|
||||
test("filter", () => {
|
||||
expect(dataCollection.filter({ filter: true }).count()).toBe(2);
|
||||
});
|
||||
test("#filter OR", () => {
|
||||
test("filter OR", () => {
|
||||
expect(dataCollection.filter([{ filter: true }, { rt: 300 }]).count()).toBe(2);
|
||||
expect(dataCollection.filter([{ filter: true }, { rt: 200 }]).count()).toBe(3);
|
||||
});
|
||||
test("#filterCustom", () => {
|
||||
test("filterCustom", () => {
|
||||
expect(
|
||||
dataCollection
|
||||
.filterCustom((x) => {
|
||||
@ -31,43 +31,59 @@ describe("DataCollection", () => {
|
||||
.count()
|
||||
).toBe(2);
|
||||
});
|
||||
test("#ignore", () => {
|
||||
|
||||
test("filterColumns", () => {
|
||||
data = [
|
||||
{ foo: "bar", rt: 100, filter: true },
|
||||
{ foo: "bar", rt: 200, filter: false },
|
||||
];
|
||||
dataCollection = new DataCollection(data);
|
||||
|
||||
const filtered_data = dataCollection.filterColumns(["rt", "foo"]);
|
||||
|
||||
expect(filtered_data.values()).toEqual([
|
||||
{ foo: "bar", rt: 100 },
|
||||
{ foo: "bar", rt: 200 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("ignore", () => {
|
||||
expect(dataCollection.ignore("rt").select("rt").count()).toBe(0);
|
||||
});
|
||||
test("#select", () => {
|
||||
test("select", () => {
|
||||
expect(JSON.stringify(dataCollection.select("rt").values)).toBe(
|
||||
JSON.stringify([100, 200, 300, 400, 500])
|
||||
);
|
||||
});
|
||||
test("#addToAll", () => {
|
||||
test("addToAll", () => {
|
||||
expect(dataCollection.readOnly().addToAll({ added: 5 }).select("added").count()).toBe(5);
|
||||
});
|
||||
test("#addToLast", () => {
|
||||
test("addToLast", () => {
|
||||
dataCollection.addToLast({ lastonly: true });
|
||||
expect(dataCollection.values()[4].lastonly).toBe(true);
|
||||
});
|
||||
test("#readOnly", () => {
|
||||
test("readOnly", () => {
|
||||
const d = dataCollection.readOnly().values();
|
||||
d[0].rt = 0;
|
||||
expect(dataCollection.values()[0].rt).toBe(100);
|
||||
});
|
||||
test("not #readOnly", () => {
|
||||
test("not readOnly", () => {
|
||||
const d = dataCollection.values();
|
||||
d[0].rt = 0;
|
||||
expect(dataCollection.values()[0].rt).toBe(0);
|
||||
});
|
||||
test("#count", () => {
|
||||
test("count", () => {
|
||||
expect(dataCollection.count()).toBe(5);
|
||||
});
|
||||
test("#push", () => {
|
||||
test("push", () => {
|
||||
dataCollection.push({ rt: 600, filter: true });
|
||||
expect(dataCollection.count()).toBe(6);
|
||||
});
|
||||
test("#values", () => {
|
||||
test("values", () => {
|
||||
expect(JSON.stringify(dataCollection.values())).toBe(JSON.stringify(data));
|
||||
expect(dataCollection.values()).toBe(data);
|
||||
});
|
||||
test("#first", () => {
|
||||
test("first", () => {
|
||||
expect(dataCollection.first(3).count()).toBe(3);
|
||||
expect(dataCollection.first(2).values()[1].rt).toBe(200);
|
||||
expect(dataCollection.first().count()).toBe(1);
|
||||
@ -82,7 +98,7 @@ describe("DataCollection", () => {
|
||||
const too_many = n + 1;
|
||||
expect(dataCollection.first(too_many).count()).toBe(n);
|
||||
});
|
||||
test("#last", () => {
|
||||
test("last", () => {
|
||||
expect(dataCollection.last(2).count()).toBe(2);
|
||||
expect(dataCollection.last(2).values()[0].rt).toBe(400);
|
||||
expect(dataCollection.last().count()).toBe(1);
|
||||
@ -97,14 +113,14 @@ describe("DataCollection", () => {
|
||||
const too_many = n + 1;
|
||||
expect(dataCollection.last(too_many).count()).toBe(n);
|
||||
});
|
||||
test("#join", () => {
|
||||
test("join", () => {
|
||||
const dc1 = dataCollection.filter({ filter: true });
|
||||
const dc2 = dataCollection.filter({ rt: 500 });
|
||||
const data = dc1.join(dc2);
|
||||
expect(data.count()).toBe(3);
|
||||
expect(data.values()[2].rt).toBe(500);
|
||||
});
|
||||
test("#unqiueNames", () => {
|
||||
test("unqiueNames", () => {
|
||||
expect(
|
||||
new DataCollection([
|
||||
{ rt: 100, filter: true },
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
randomID,
|
||||
randomInt,
|
||||
repeat,
|
||||
setSeed,
|
||||
shuffle,
|
||||
shuffleAlternateGroups,
|
||||
shuffleNoRepeats,
|
||||
@ -12,7 +13,7 @@ afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("#shuffle", () => {
|
||||
describe("shuffle", () => {
|
||||
test("should produce fixed order with mock RNG", () => {
|
||||
jest.spyOn(Math, "random").mockReturnValue(0.5);
|
||||
const arr = [1, 2, 3, 4, 5, 6];
|
||||
@ -31,7 +32,7 @@ describe("shuffleAlternateGroups", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#randomID", () => {
|
||||
describe("randomID", () => {
|
||||
test("should produce ID based on mock RNG", () => {
|
||||
jest
|
||||
.spyOn(Math, "random")
|
||||
@ -165,3 +166,20 @@ describe("randomInt", () => {
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe("setSeed", () => {
|
||||
test("Replaces Math.random() with seedable RNG", () => {
|
||||
setSeed("jspsych");
|
||||
|
||||
const r1_1 = Math.random();
|
||||
const r1_2 = Math.random();
|
||||
|
||||
setSeed("jspsych");
|
||||
|
||||
const r2_1 = Math.random();
|
||||
const r2_2 = Math.random();
|
||||
|
||||
expect(r1_1).toEqual(r2_1);
|
||||
expect(r1_2).toEqual(r2_2);
|
||||
});
|
||||
});
|
||||
|
93
packages/jspsych/tests/randomization/setseed.test.ts
Normal file
93
packages/jspsych/tests/randomization/setseed.test.ts
Normal file
@ -0,0 +1,93 @@
|
||||
// this file is to test that jsPsych.randomization.setSeed works in context
|
||||
|
||||
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
|
||||
import { pressKey, startTimeline } from "@jspsych/test-utils";
|
||||
|
||||
import { initJsPsych } from "../../src";
|
||||
|
||||
describe("setSeed generates predictable randomization", () => {
|
||||
test("timeline variable randomization is preserved", async () => {
|
||||
const jsPsych = initJsPsych();
|
||||
|
||||
const seed = jsPsych.randomization.setSeed();
|
||||
|
||||
const { getData } = await startTimeline(
|
||||
[
|
||||
{
|
||||
timeline: [
|
||||
{
|
||||
type: htmlKeyboardResponse,
|
||||
stimulus: "this is html",
|
||||
data: {
|
||||
i: jsPsych.timelineVariable("i"),
|
||||
},
|
||||
},
|
||||
],
|
||||
timeline_variables: [
|
||||
{ i: 0 },
|
||||
{ i: 1 },
|
||||
{ i: 2 },
|
||||
{ i: 3 },
|
||||
{ i: 4 },
|
||||
{ i: 5 },
|
||||
{ i: 6 },
|
||||
{ i: 7 },
|
||||
{ i: 8 },
|
||||
],
|
||||
randomize_order: true,
|
||||
},
|
||||
],
|
||||
jsPsych
|
||||
);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
pressKey(" ");
|
||||
}
|
||||
|
||||
const data_run_1 = getData().readOnly();
|
||||
|
||||
const jsPsych_run2 = initJsPsych();
|
||||
|
||||
jsPsych_run2.randomization.setSeed(seed);
|
||||
|
||||
const { getData: getData2 } = await startTimeline(
|
||||
[
|
||||
{
|
||||
timeline: [
|
||||
{
|
||||
type: htmlKeyboardResponse,
|
||||
stimulus: "this is html",
|
||||
data: {
|
||||
i: jsPsych_run2.timelineVariable("i"),
|
||||
},
|
||||
},
|
||||
],
|
||||
timeline_variables: [
|
||||
{ i: 0 },
|
||||
{ i: 1 },
|
||||
{ i: 2 },
|
||||
{ i: 3 },
|
||||
{ i: 4 },
|
||||
{ i: 5 },
|
||||
{ i: 6 },
|
||||
{ i: 7 },
|
||||
{ i: 8 },
|
||||
],
|
||||
randomize_order: true,
|
||||
},
|
||||
],
|
||||
jsPsych_run2
|
||||
);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
pressKey(" ");
|
||||
}
|
||||
|
||||
const data_run_2 = getData2().readOnly();
|
||||
|
||||
console.log(data_run_1.values());
|
||||
console.log(data_run_2.values());
|
||||
|
||||
expect(data_run_1.select("i").values).toEqual(data_run_2.select("i").values);
|
||||
});
|
||||
});
|
@ -33,6 +33,7 @@
|
||||
"jspsych": ">=7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jspsych/config": "^1.1.0"
|
||||
"@jspsych/config": "^1.1.0",
|
||||
"jspsych": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
17
turbo.json
Normal file
17
turbo.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"baseBranch": "origin/main",
|
||||
"npmClient": "npm",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"dist/**"
|
||||
]
|
||||
},
|
||||
"tsc": {
|
||||
"outputs": []
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user