mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 19:20:55 +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
|
cache: npm
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Download Turborepo cache
|
- name: Download Turborepo cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
@ -35,9 +35,6 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-16-turbo-
|
${{ runner.os }}-node-16-turbo-
|
||||||
|
|
||||||
- name: Build packages
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npm run test -- --ci --maxWorkers=2
|
run: npm run test -- --ci --maxWorkers=2
|
||||||
env:
|
env:
|
||||||
@ -47,7 +44,8 @@ jobs:
|
|||||||
id: changesets
|
id: changesets
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
with:
|
with:
|
||||||
publish: npm run release
|
version: npm run changeset:version
|
||||||
|
publish: npm run changeset:publish
|
||||||
commit: "chore(release): version packages"
|
commit: "chore(release): version packages"
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
const trial = {
|
const trial = {
|
||||||
type: jsPsychBrowserCheck,
|
type: jsPsychBrowserCheck,
|
||||||
inclusion_function: (data) => {
|
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>`
|
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-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-calibrate@1.0.0"></script>
|
||||||
<script src="https://unpkg.com/@jspsych/plugin-webgazer-validate@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>
|
<script src="https://unpkg.com/@jspsych/extension-webgazer@1.0.0"></script>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
|
@ -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.
|
[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 = {
|
var trial = {
|
||||||
type: jsPsychBrowserCheck,
|
type: jsPsychBrowserCheck,
|
||||||
inclusion_function: (data) => {
|
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>`
|
exclusion_message: `<p>You must use Chrome or Firefox to complete this experiment.</p>`
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# webgazer-calibrate
|
# 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
|
## Parameters
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ In addition to the [default data collected by all plugins](../overview/plugins.m
|
|||||||
Name | Type | Value
|
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
|
## Simulation Mode
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# webgazer-init-camera
|
# 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
|
## Parameters
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# webgazer-validate
|
# 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
|
## 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();
|
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()
|
#### .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.
|
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
|
## jsPsych.randomization.shuffle
|
||||||
|
|
||||||
```javascript
|
```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",
|
"test:watch": "npm test -- --watch",
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"build:archive": "gulp createCoreDistArchive",
|
"build:archive": "gulp createCoreDistArchive",
|
||||||
|
"update-unpkg-links": "gulp updateUnpkgLinks",
|
||||||
"prepare": "node -e 'process.exit(!process.env.CI)' || (husky install && npm run build)",
|
"prepare": "node -e 'process.exit(!process.env.CI)' || (husky install && npm run build)",
|
||||||
"tsc": "turbo tsc",
|
"tsc": "turbo tsc",
|
||||||
"changeset": "changeset",
|
"changeset": "changeset",
|
||||||
"release": "changeset publish"
|
"changeset:version": "changeset version && npm install && npm run update-unpkg-links",
|
||||||
|
"changeset:publish": "npm run build && changeset publish"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0",
|
"node": ">=14.0.0",
|
||||||
"npm": ">=7.0.0"
|
"npm": ">=7.0.0"
|
||||||
},
|
},
|
||||||
|
"packageManager": "npm@8.3.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/changelog-github": "^0.4.1",
|
"@changesets/changelog-github": "^0.4.3",
|
||||||
"@changesets/cli": "^2.17.0",
|
"@changesets/cli": "^2.21.1",
|
||||||
"husky": "^7.0.1",
|
"husky": "^7.0.4",
|
||||||
"import-sort-style-module": "^6.0.0",
|
"import-sort-style-module": "^6.0.0",
|
||||||
"lint-staged": "^11.1.2",
|
"lint-staged": "^12.3.5",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.5.1",
|
||||||
"prettier-plugin-import-sort": "^0.0.7",
|
"prettier-plugin-import-sort": "^0.0.7",
|
||||||
"turbo": "^1.0.6"
|
"turbo": "^1.1.6"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 100
|
"printWidth": 100
|
||||||
@ -44,22 +47,5 @@
|
|||||||
"projects": [
|
"projects": [
|
||||||
"<rootDir>/packages/*/jest.config.cjs"
|
"<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 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 = () =>
|
const getVersionFileContents = () =>
|
||||||
[
|
[
|
||||||
"Included in this release:\n",
|
"Included in this release:\n",
|
||||||
@ -25,19 +39,7 @@ const getVersionFileContents = () =>
|
|||||||
"/";
|
"/";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
glob
|
getAllPackages()
|
||||||
// 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,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter packages that should not be listed
|
// Filter packages that should not be listed
|
||||||
.filter(({ name }) => !["@jspsych/config", "@jspsych/test-utils"].includes(name))
|
.filter(({ name }) => !["@jspsych/config", "@jspsych/test-utils"].includes(name))
|
||||||
|
|
||||||
@ -85,15 +87,15 @@ export const createCoreDistArchive = () =>
|
|||||||
// Rewrite script source paths
|
// Rewrite script source paths
|
||||||
.pipe(
|
.pipe(
|
||||||
replace(
|
replace(
|
||||||
/<script src="(.*)\/packages\/(.*)\/dist\/index\.browser\.js"><\/script>/g,
|
/<script src="(.*)\/packages\/(.*)\/dist\/index\.browser\.js"/g,
|
||||||
'<script src="$1/dist/$2.js"></script>'
|
'<script src="$1/dist/$2.js"'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// Rewrite jspsych css source paths
|
// Rewrite jspsych css source paths
|
||||||
.pipe(
|
.pipe(
|
||||||
replace(
|
replace(
|
||||||
/<link rel="stylesheet" href="(.*)\/packages\/jspsych\/css\/(.*)" \/>/g,
|
/<link rel="stylesheet" href="(.*)\/packages\/jspsych\/css\/(.*)"/g,
|
||||||
'<link rel="stylesheet" href="$1/dist/$2" />'
|
'<link rel="stylesheet" href="$1/dist/$2"'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -105,3 +107,23 @@ export const createCoreDistArchive = () =>
|
|||||||
)
|
)
|
||||||
.pipe(zip("dist.zip"))
|
.pipe(zip("dist.zip"))
|
||||||
.pipe(dest("."));
|
.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",
|
"homepage": "https://www.jspsych.org",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"auto-bind": "^4.0.0",
|
"auto-bind": "^4.0.0",
|
||||||
"random-words": "^1.1.1"
|
"random-words": "^1.1.1",
|
||||||
|
"seedrandom": "^3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource/open-sans": "4.5.3",
|
"@fontsource/open-sans": "4.5.3",
|
||||||
"@jspsych/config": "^1.1.0",
|
"@jspsych/config": "^1.1.0",
|
||||||
"@types/dom-mediacapture-record": "^1.0.11",
|
"@types/dom-mediacapture-record": "^1.0.11",
|
||||||
|
"@types/seedrandom": "^3.0.1",
|
||||||
"base64-inline-loader": "^2.0.1",
|
"base64-inline-loader": "^2.0.1",
|
||||||
"css-loader": "^6.6.0",
|
"css-loader": "^6.6.0",
|
||||||
"mini-css-extract-plugin": "^2.5.3",
|
"mini-css-extract-plugin": "^2.5.3",
|
||||||
|
@ -134,6 +134,14 @@ export class DataCollection {
|
|||||||
return new DataCollection(this.trials.filter(fn));
|
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) {
|
select(column) {
|
||||||
const values = [];
|
const values = [];
|
||||||
for (const trial of this.trials) {
|
for (const trial of this.trials) {
|
||||||
|
@ -1,4 +1,20 @@
|
|||||||
import rw from "random-words";
|
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) {
|
export function repeat(array, repetitions, unpack = false) {
|
||||||
const arr_isArray = Array.isArray(array);
|
const arr_isArray = Array.isArray(array);
|
||||||
|
@ -15,14 +15,14 @@ describe("DataCollection", () => {
|
|||||||
dataCollection = new DataCollection(data);
|
dataCollection = new DataCollection(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#filter", () => {
|
test("filter", () => {
|
||||||
expect(dataCollection.filter({ filter: true }).count()).toBe(2);
|
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: 300 }]).count()).toBe(2);
|
||||||
expect(dataCollection.filter([{ filter: true }, { rt: 200 }]).count()).toBe(3);
|
expect(dataCollection.filter([{ filter: true }, { rt: 200 }]).count()).toBe(3);
|
||||||
});
|
});
|
||||||
test("#filterCustom", () => {
|
test("filterCustom", () => {
|
||||||
expect(
|
expect(
|
||||||
dataCollection
|
dataCollection
|
||||||
.filterCustom((x) => {
|
.filterCustom((x) => {
|
||||||
@ -31,43 +31,59 @@ describe("DataCollection", () => {
|
|||||||
.count()
|
.count()
|
||||||
).toBe(2);
|
).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);
|
expect(dataCollection.ignore("rt").select("rt").count()).toBe(0);
|
||||||
});
|
});
|
||||||
test("#select", () => {
|
test("select", () => {
|
||||||
expect(JSON.stringify(dataCollection.select("rt").values)).toBe(
|
expect(JSON.stringify(dataCollection.select("rt").values)).toBe(
|
||||||
JSON.stringify([100, 200, 300, 400, 500])
|
JSON.stringify([100, 200, 300, 400, 500])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("#addToAll", () => {
|
test("addToAll", () => {
|
||||||
expect(dataCollection.readOnly().addToAll({ added: 5 }).select("added").count()).toBe(5);
|
expect(dataCollection.readOnly().addToAll({ added: 5 }).select("added").count()).toBe(5);
|
||||||
});
|
});
|
||||||
test("#addToLast", () => {
|
test("addToLast", () => {
|
||||||
dataCollection.addToLast({ lastonly: true });
|
dataCollection.addToLast({ lastonly: true });
|
||||||
expect(dataCollection.values()[4].lastonly).toBe(true);
|
expect(dataCollection.values()[4].lastonly).toBe(true);
|
||||||
});
|
});
|
||||||
test("#readOnly", () => {
|
test("readOnly", () => {
|
||||||
const d = dataCollection.readOnly().values();
|
const d = dataCollection.readOnly().values();
|
||||||
d[0].rt = 0;
|
d[0].rt = 0;
|
||||||
expect(dataCollection.values()[0].rt).toBe(100);
|
expect(dataCollection.values()[0].rt).toBe(100);
|
||||||
});
|
});
|
||||||
test("not #readOnly", () => {
|
test("not readOnly", () => {
|
||||||
const d = dataCollection.values();
|
const d = dataCollection.values();
|
||||||
d[0].rt = 0;
|
d[0].rt = 0;
|
||||||
expect(dataCollection.values()[0].rt).toBe(0);
|
expect(dataCollection.values()[0].rt).toBe(0);
|
||||||
});
|
});
|
||||||
test("#count", () => {
|
test("count", () => {
|
||||||
expect(dataCollection.count()).toBe(5);
|
expect(dataCollection.count()).toBe(5);
|
||||||
});
|
});
|
||||||
test("#push", () => {
|
test("push", () => {
|
||||||
dataCollection.push({ rt: 600, filter: true });
|
dataCollection.push({ rt: 600, filter: true });
|
||||||
expect(dataCollection.count()).toBe(6);
|
expect(dataCollection.count()).toBe(6);
|
||||||
});
|
});
|
||||||
test("#values", () => {
|
test("values", () => {
|
||||||
expect(JSON.stringify(dataCollection.values())).toBe(JSON.stringify(data));
|
expect(JSON.stringify(dataCollection.values())).toBe(JSON.stringify(data));
|
||||||
expect(dataCollection.values()).toBe(data);
|
expect(dataCollection.values()).toBe(data);
|
||||||
});
|
});
|
||||||
test("#first", () => {
|
test("first", () => {
|
||||||
expect(dataCollection.first(3).count()).toBe(3);
|
expect(dataCollection.first(3).count()).toBe(3);
|
||||||
expect(dataCollection.first(2).values()[1].rt).toBe(200);
|
expect(dataCollection.first(2).values()[1].rt).toBe(200);
|
||||||
expect(dataCollection.first().count()).toBe(1);
|
expect(dataCollection.first().count()).toBe(1);
|
||||||
@ -82,7 +98,7 @@ describe("DataCollection", () => {
|
|||||||
const too_many = n + 1;
|
const too_many = n + 1;
|
||||||
expect(dataCollection.first(too_many).count()).toBe(n);
|
expect(dataCollection.first(too_many).count()).toBe(n);
|
||||||
});
|
});
|
||||||
test("#last", () => {
|
test("last", () => {
|
||||||
expect(dataCollection.last(2).count()).toBe(2);
|
expect(dataCollection.last(2).count()).toBe(2);
|
||||||
expect(dataCollection.last(2).values()[0].rt).toBe(400);
|
expect(dataCollection.last(2).values()[0].rt).toBe(400);
|
||||||
expect(dataCollection.last().count()).toBe(1);
|
expect(dataCollection.last().count()).toBe(1);
|
||||||
@ -97,14 +113,14 @@ describe("DataCollection", () => {
|
|||||||
const too_many = n + 1;
|
const too_many = n + 1;
|
||||||
expect(dataCollection.last(too_many).count()).toBe(n);
|
expect(dataCollection.last(too_many).count()).toBe(n);
|
||||||
});
|
});
|
||||||
test("#join", () => {
|
test("join", () => {
|
||||||
const dc1 = dataCollection.filter({ filter: true });
|
const dc1 = dataCollection.filter({ filter: true });
|
||||||
const dc2 = dataCollection.filter({ rt: 500 });
|
const dc2 = dataCollection.filter({ rt: 500 });
|
||||||
const data = dc1.join(dc2);
|
const data = dc1.join(dc2);
|
||||||
expect(data.count()).toBe(3);
|
expect(data.count()).toBe(3);
|
||||||
expect(data.values()[2].rt).toBe(500);
|
expect(data.values()[2].rt).toBe(500);
|
||||||
});
|
});
|
||||||
test("#unqiueNames", () => {
|
test("unqiueNames", () => {
|
||||||
expect(
|
expect(
|
||||||
new DataCollection([
|
new DataCollection([
|
||||||
{ rt: 100, filter: true },
|
{ rt: 100, filter: true },
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
randomID,
|
randomID,
|
||||||
randomInt,
|
randomInt,
|
||||||
repeat,
|
repeat,
|
||||||
|
setSeed,
|
||||||
shuffle,
|
shuffle,
|
||||||
shuffleAlternateGroups,
|
shuffleAlternateGroups,
|
||||||
shuffleNoRepeats,
|
shuffleNoRepeats,
|
||||||
@ -12,7 +13,7 @@ afterEach(() => {
|
|||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#shuffle", () => {
|
describe("shuffle", () => {
|
||||||
test("should produce fixed order with mock RNG", () => {
|
test("should produce fixed order with mock RNG", () => {
|
||||||
jest.spyOn(Math, "random").mockReturnValue(0.5);
|
jest.spyOn(Math, "random").mockReturnValue(0.5);
|
||||||
const arr = [1, 2, 3, 4, 5, 6];
|
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", () => {
|
test("should produce ID based on mock RNG", () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(Math, "random")
|
.spyOn(Math, "random")
|
||||||
@ -165,3 +166,20 @@ describe("randomInt", () => {
|
|||||||
}).toThrowError();
|
}).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"
|
"jspsych": ">=7.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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