mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 11:10:54 +00:00
Upgrade Jest to v29 and replace ts-jest
with @sucrase/jest-plugin
This commit is contained in:
parent
0bc6f5486b
commit
76e7508024
5
.changeset/chilly-pans-sin.md
Normal file
5
.changeset/chilly-pans-sin.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jspsych/config": major
|
||||
---
|
||||
|
||||
Activate TypeScript's `isolatedModules` flag in the root `tsconfig.json` file. If you are facing any TypeScript errors due to `isolatedModules`, please update your code according to the error messages.
|
@ -32,3 +32,4 @@ Rewrite jsPsych's core logic. The following breaking changes have been made:
|
||||
- Interaction listeners are now removed when the experiment ends.
|
||||
- JsPsych will now throw an error when a non-array value is used for a trial parameter marked as `array: true` in the plugin's info object.
|
||||
- JsPsych now internally relies on the JavaScript event loop. This means automated tests have to `await` utility functions like `pressKey()` to process the event loop.
|
||||
- The `jspsych` package no longer exports `universalPluginParameters` and the `UniversalPluginParameters` type.
|
||||
|
5
.changeset/modern-experts-work.md
Normal file
5
.changeset/modern-experts-work.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jspsych/config": major
|
||||
---
|
||||
|
||||
Upgrade Jest to v29 and replace ts-jest with the more performant Sucrase Jest plugin. As a consequence, Jest does no longer type-check code. Please check Jest's [upgrade guide](https://jestjs.io/docs/upgrading-to-jest29) for instructions on updating your tests.
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -35,6 +35,9 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.node }}-turbo-
|
||||
|
||||
- name: Check types
|
||||
run: npm run tsc
|
||||
|
||||
- name: Build packages
|
||||
run: npm run build
|
||||
|
||||
@ -44,8 +47,7 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test -- --ci --coverage --maxWorkers=2 --reporters=default --reporters=github-actions
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096" # Increase heap size for jest
|
||||
|
||||
# TODO setup codecov or coveralls
|
||||
# - name: Upload coverage to Codecov
|
||||
# uses: codecov/codecov-action@v1
|
||||
|
19795
package-lock.json
generated
19795
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,14 +27,13 @@
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.4.7",
|
||||
"@changesets/cli": "^2.25.2",
|
||||
"@jspsych/config": "^1.3.2",
|
||||
"husky": "^8.0.2",
|
||||
"import-sort-style-module": "^6.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-import-sort": "^0.0.7",
|
||||
"turbo": "^1.6.3",
|
||||
"jest": "*",
|
||||
"ts-jest": "*"
|
||||
"turbo": "^1.6.3"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100
|
||||
|
@ -1,21 +1,14 @@
|
||||
const ts = require("typescript");
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
const hq = require("alias-hq");
|
||||
|
||||
/** @type { (dirname: string) => import('@jest/types').Config.InitialOptions } */
|
||||
module.exports.makePackageConfig = (dirname) => {
|
||||
const packageJson = require(dirname + "/package.json");
|
||||
const packageBaseName = packageJson.name.replace("@jspsych/", "");
|
||||
|
||||
// based on https://github.com/formium/tsdx/blob/462af2d002987f985695b98400e0344b8f2754b7/src/createRollupConfig.ts#L51-L57
|
||||
const tsCompilerOptions = ts.parseJsonConfigFileContent(
|
||||
ts.readConfigFile(dirname + "/tsconfig.json", ts.sys.readFile).config,
|
||||
ts.sys,
|
||||
dirname
|
||||
).options;
|
||||
|
||||
return {
|
||||
preset: "ts-jest",
|
||||
moduleNameMapper: pathsToModuleNameMapper(tsCompilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||
transform: { "\\.(js|jsx|ts|tsx)$": "@sucrase/jest-plugin" },
|
||||
moduleNameMapper: hq.load(dirname + "/tsconfig.json").get("jest"),
|
||||
|
||||
testEnvironment: "jsdom",
|
||||
testEnvironmentOptions: {
|
||||
fetchExternalResources: true,
|
||||
|
@ -47,25 +47,27 @@
|
||||
"@rollup/plugin-json": "4.1.0",
|
||||
"@rollup/plugin-node-resolve": "13.3.0",
|
||||
"@rollup/plugin-replace": "4.0.0",
|
||||
"@sucrase/jest-plugin": "3.0.0",
|
||||
"@types/gulp": "4.0.9",
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/jest": "29.2.3",
|
||||
"alias-hq": "github:bjoluc/alias-hq#fix-jest-plugin",
|
||||
"babel-preset-minify": "0.5.2",
|
||||
"canvas": "2.9.1",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cli": "2.3.0",
|
||||
"gulp-file": "^0.4.0",
|
||||
"gulp-file": "0.4.0",
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-replace": "1.1.3",
|
||||
"gulp-zip": "5.1.0",
|
||||
"jest": "28.1.0",
|
||||
"jest-environment-jsdom": "28.1.0",
|
||||
"jest": "29.3.1",
|
||||
"jest-environment-jsdom": "29.3.1",
|
||||
"merge-stream": "2.0.0",
|
||||
"regenerator-runtime": "0.13.9",
|
||||
"rollup": "2.73.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-typescript2": "0.31.2",
|
||||
"ts-jest": "28.0.2",
|
||||
"sucrase": "3.29.0",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "^4.6.4"
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,21 @@
|
||||
{
|
||||
// shared base tsconfig for all jsPsych packages
|
||||
// based on https://github.com/formium/tsdx/blob/462af2d002987f985695b98400e0344b8f2754b7/templates/basic/tsconfig.json
|
||||
// see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"module": "ESNext",
|
||||
"lib": ["dom", "esnext"],
|
||||
"importHelpers": true,
|
||||
// output .d.ts declaration files for consumers
|
||||
"declaration": true,
|
||||
// output .js.map sourcemap files for consumers
|
||||
"sourceMap": true,
|
||||
// stricter type-checking for stronger correctness. Recommended by TS
|
||||
"strict": false, // should be enabled one lucky day
|
||||
// linter checks for common issues
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
// noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
|
||||
"noUnusedLocals": false, // should be enabled one lucky day
|
||||
"noUnusedParameters": false, // should be enabled one lucky day
|
||||
// use Node's module resolution algorithm, instead of the legacy TS one
|
||||
"moduleResolution": "node",
|
||||
// interop between ESM and CJS modules. Recommended by TS
|
||||
"esModuleInterop": true,
|
||||
// significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
|
||||
"skipLibCheck": true,
|
||||
// error out if import and file system have a casing mismatch. Recommended by TS
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
// do not emit build output when running `tsc`
|
||||
"noEmit": true
|
||||
"noEmit": true,
|
||||
"isolatedModules": true // required by Sucrase
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ describe("ProgressBar", () => {
|
||||
|
||||
it("sets up proper HTML markup when created", () => {
|
||||
expect(containerElement.innerHTML).toMatchInlineSnapshot(
|
||||
'"<span>My message</span><div id=\\"jspsych-progressbar-outer\\"><div id=\\"jspsych-progressbar-inner\\" style=\\"width: 0%;\\"></div></div>"'
|
||||
'"<span>My message</span><div id="jspsych-progressbar-outer"><div id="jspsych-progressbar-inner" style="width: 0%;"></div></div>"'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -62,12 +62,6 @@ export function initJsPsych(options?) {
|
||||
}
|
||||
|
||||
export { JsPsych } from "./JsPsych";
|
||||
export {
|
||||
JsPsychPlugin,
|
||||
PluginInfo,
|
||||
TrialType,
|
||||
ParameterType,
|
||||
universalPluginParameters,
|
||||
UniversalPluginParameters,
|
||||
} from "./modules/plugins";
|
||||
export { JsPsychExtension, JsPsychExtensionInfo } from "./modules/extensions";
|
||||
export type { JsPsychPlugin, PluginInfo, TrialType } from "./modules/plugins";
|
||||
export { ParameterType } from "./modules/plugins";
|
||||
export type { JsPsychExtension, JsPsychExtensionInfo } from "./modules/extensions";
|
||||
|
@ -295,9 +295,9 @@ describe("Timeline", () => {
|
||||
describe("with timeline variables", () => {
|
||||
it("repeats all trials for each set of variables", async () => {
|
||||
const xValues = [];
|
||||
TestPlugin.prototype.trial.mockImplementation(async () => {
|
||||
TestPlugin.trial = async () => {
|
||||
xValues.push(timeline.evaluateTimelineVariable(new TimelineVariable("x")));
|
||||
});
|
||||
};
|
||||
|
||||
const timeline = createTimeline({
|
||||
timeline: [{ type: TestPlugin }],
|
||||
@ -320,9 +320,9 @@ describe("Timeline", () => {
|
||||
sample,
|
||||
randomize_order,
|
||||
});
|
||||
TestPlugin.prototype.trial.mockImplementation(async () => {
|
||||
TestPlugin.trial = async () => {
|
||||
xValues.push(timeline.evaluateTimelineVariable(new TimelineVariable("x")));
|
||||
});
|
||||
};
|
||||
return timeline;
|
||||
};
|
||||
|
||||
|
@ -106,13 +106,11 @@ describe("Trial", () => {
|
||||
await trial1.run();
|
||||
expect(trial1.getResult()).toEqual(expect.objectContaining({ my: "result" }));
|
||||
|
||||
jest
|
||||
.spyOn(TestPlugin.prototype, "trial")
|
||||
.mockImplementation(async (display_element, trial, on_load) => {
|
||||
TestPlugin.trial = async (display_element, trial, on_load) => {
|
||||
on_load();
|
||||
dependencies.finishTrialPromise.resolve({ finishTrial: "result" });
|
||||
return { my: "result" };
|
||||
});
|
||||
};
|
||||
|
||||
const trial2 = createTrial({ type: TestPlugin });
|
||||
await trial2.run();
|
||||
@ -122,9 +120,9 @@ describe("Trial", () => {
|
||||
|
||||
describe("if `trial` returns no promise", () => {
|
||||
beforeAll(() => {
|
||||
TestPlugin.prototype.trial.mockImplementation(() => {
|
||||
TestPlugin.trial = () => {
|
||||
dependencies.finishTrialPromise.resolve({ my: "result" });
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
it("invokes the local `on_load` callback", async () => {
|
||||
@ -216,11 +214,11 @@ describe("Trial", () => {
|
||||
complexArrayParameter: { type: ParameterType.COMPLEX, array: true },
|
||||
functionParameter: { type: ParameterType.FUNCTION },
|
||||
});
|
||||
TestPlugin.setDefaultTrialResult({
|
||||
TestPlugin.defaultTrialResult = {
|
||||
result: "foo",
|
||||
stringParameter2: "string",
|
||||
stringParameter3: "string",
|
||||
});
|
||||
};
|
||||
const trial = createTrial({
|
||||
type: TestPlugin,
|
||||
stringParameter1: "string",
|
||||
@ -367,12 +365,12 @@ describe("Trial", () => {
|
||||
await expect(
|
||||
createTrial({ type: TestPlugin, stringArray: {} }).run()
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
'"A non-array value (`[object Object]`) was provided for the array parameter \\"stringArray\\" in the \\"test\\" plugin. Please make sure that \\"stringArray\\" is an array."'
|
||||
'"A non-array value (`[object Object]`) was provided for the array parameter "stringArray" in the "test" plugin. Please make sure that "stringArray" is an array."'
|
||||
);
|
||||
await expect(
|
||||
createTrial({ type: TestPlugin, stringArray: 1 }).run()
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
'"A non-array value (`1`) was provided for the array parameter \\"stringArray\\" in the \\"test\\" plugin. Please make sure that \\"stringArray\\" is an array."'
|
||||
'"A non-array value (`1`) was provided for the array parameter "stringArray" in the "test" plugin. Please make sure that "stringArray" is an array."'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -21,11 +21,7 @@ class TestPlugin implements JsPsychPlugin<typeof testPluginInfo> {
|
||||
TestPlugin.info = testPluginInfo;
|
||||
}
|
||||
|
||||
private static defaultTrialResult: Record<string, any> = { my: "result" };
|
||||
|
||||
static setDefaultTrialResult(defaultTrialResult: Record<string, any> = { my: "result" }) {
|
||||
TestPlugin.defaultTrialResult = defaultTrialResult;
|
||||
}
|
||||
static defaultTrialResult: Record<string, any> = { my: "result" };
|
||||
|
||||
private static finishTrialMode: "immediate" | "manual" = "immediate";
|
||||
|
||||
@ -38,8 +34,8 @@ class TestPlugin implements JsPsychPlugin<typeof testPluginInfo> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the `trial` method of all instances of `TestPlugin` finish immediately and allows to manually finish the trial by
|
||||
* invoking `TestPlugin.finishTrial()` instead.
|
||||
* Makes the `trial` method of all instances of `TestPlugin` finish immediately and allows to
|
||||
* manually finish the trial by invoking `TestPlugin.finishTrial()` instead.
|
||||
*/
|
||||
static setImmediateFinishTrialMode() {
|
||||
TestPlugin.finishTrialMode = "immediate";
|
||||
@ -48,39 +44,19 @@ class TestPlugin implements JsPsychPlugin<typeof testPluginInfo> {
|
||||
private static trialPromise = new PromiseWrapper<Record<string, any>>();
|
||||
|
||||
/**
|
||||
* Resolves the promise returned by `trial()` with the provided `result` object or `{ my: "result"
|
||||
* }` if no `result` object was provided.
|
||||
* Resolves the promise returned by `trial()` with the provided `result` or
|
||||
* `TestPlugin.defaultTrialResult` if no `result` object was passed.
|
||||
**/
|
||||
static async finishTrial(result?: Record<string, any>) {
|
||||
TestPlugin.trialPromise.resolve(result ?? TestPlugin.defaultTrialResult);
|
||||
await flushPromises();
|
||||
}
|
||||
|
||||
/** Resets all static properties including the `trial` function mock */
|
||||
static reset() {
|
||||
TestPlugin.prototype.trial
|
||||
.mockReset()
|
||||
.mockImplementation(TestPlugin.prototype.defaultTrialImplementation);
|
||||
TestPlugin.prototype.simulate
|
||||
.mockReset()
|
||||
.mockImplementation(TestPlugin.prototype.defaultSimulateImplementation);
|
||||
this.resetPluginInfo();
|
||||
this.setDefaultTrialResult();
|
||||
this.setImmediateFinishTrialMode();
|
||||
}
|
||||
|
||||
constructor(private jsPsych: JsPsych) {}
|
||||
|
||||
// For convenience, `trial` is set to a `jest.fn` below using `TestPlugin.prototype` and
|
||||
// `defaultTrialImplementation`
|
||||
trial: jest.Mock<Promise<TrialResult | void> | void>;
|
||||
simulate: jest.Mock<Promise<TrialResult | void> | void>;
|
||||
|
||||
defaultTrialImplementation(
|
||||
static defaultTrialImplementation(
|
||||
display_element: HTMLElement,
|
||||
trial: TrialType<typeof testPluginInfo>,
|
||||
on_load: () => void
|
||||
) {
|
||||
): void | Promise<TrialResult | void> {
|
||||
on_load();
|
||||
if (TestPlugin.finishTrialMode === "immediate") {
|
||||
return Promise.resolve(TestPlugin.defaultTrialResult);
|
||||
@ -88,17 +64,32 @@ class TestPlugin implements JsPsychPlugin<typeof testPluginInfo> {
|
||||
return TestPlugin.trialPromise.get();
|
||||
}
|
||||
|
||||
defaultSimulateImplementation(
|
||||
public static trial = TestPlugin.defaultTrialImplementation;
|
||||
|
||||
static defaultSimulateImplementation(
|
||||
trial: TrialType<typeof testPluginInfo>,
|
||||
simulation_mode: SimulationMode,
|
||||
simulation_options: SimulationOptions,
|
||||
on_load?: () => void
|
||||
): void | Promise<void | TrialResult> {
|
||||
return this.defaultTrialImplementation(document.createElement("div"), trial, on_load);
|
||||
}
|
||||
return TestPlugin.defaultTrialImplementation(document.createElement("div"), trial, on_load);
|
||||
}
|
||||
|
||||
TestPlugin.prototype.trial = jest.fn(TestPlugin.prototype.defaultTrialImplementation);
|
||||
TestPlugin.prototype.simulate = jest.fn(TestPlugin.prototype.defaultTrialImplementation);
|
||||
public static simulate = TestPlugin.defaultSimulateImplementation;
|
||||
|
||||
/** Resets all static properties including function implementations */
|
||||
static reset() {
|
||||
TestPlugin.defaultTrialResult = { my: "result" };
|
||||
TestPlugin.trial = TestPlugin.defaultTrialImplementation;
|
||||
TestPlugin.simulate = TestPlugin.defaultSimulateImplementation;
|
||||
TestPlugin.resetPluginInfo();
|
||||
TestPlugin.setImmediateFinishTrialMode();
|
||||
}
|
||||
|
||||
constructor(private jsPsych: JsPsych) {}
|
||||
|
||||
trial = jest.fn(TestPlugin.trial);
|
||||
simulate = jest.fn(TestPlugin.simulate);
|
||||
}
|
||||
|
||||
export default TestPlugin;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
|
||||
import { pressKey, startTimeline } from "@jspsych/test-utils";
|
||||
|
||||
jest.useFakeTimers("modern");
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe("minimum_valid_rt parameter", () => {
|
||||
test("has a default value of 0", async () => {
|
||||
|
@ -30,7 +30,8 @@
|
||||
"url": "https://github.com/jspsych/jsPsych/issues"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jspsych": ">=7.0.0"
|
||||
"jspsych": ">=7.0.0",
|
||||
"@types/jest": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jspsych/config": "^1.1.0",
|
||||
|
Loading…
Reference in New Issue
Block a user