Skip to content

Commit 6e74767

Browse files
authored
Merge pull request #3484 from jspsych/fix-build-citations-test
handle citation test edge case
2 parents ec7e611 + e710cb0 commit 6e74767

File tree

10 files changed

+188
-107
lines changed

10 files changed

+188
-107
lines changed

.changeset/rich-fans-jump.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"jspsych": patch
3+
"@jspsych/config": patch
4+
---
5+
6+
Patches some edge cases for `getCitations` and the build process that reads CITATION.CFF files to include citation info

package-lock.json

Lines changed: 14 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/config/generateCitations.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import path from "node:path";
77

88
import { Cite } from "@citation-js/core";
99
import appRootPath from "app-root-path";
10-
import yaml from "yaml";
1110

1211
/**
1312
* Generate citation data from CITATION.cff file
@@ -23,11 +22,8 @@ export default function generateCitations() {
2322
let rawCff;
2423
const getCff = (path) => {
2524
rawCff = fs.readFileSync(path, "utf-8").toString();
26-
const cffData = yaml.parse(rawCff);
27-
if (cffData["preferred-citation"]) {
28-
preferredCitation = true;
29-
}
30-
return yaml.stringify(rawCff);
25+
preferredCitation = rawCff.includes("preferred-citation:");
26+
return rawCff;
3127
};
3228

3329
try {
@@ -80,7 +76,7 @@ export default function generateCitations() {
8076
return citationBibtex;
8177
} catch (error) {
8278
console.log(`Error converting CITATION.cff to BibTeX string: ${error.message}`);
83-
return null;
79+
return "";
8480
}
8581
})();
8682

packages/config/jest.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require("./jest.cjs").makePackageConfig(__dirname);

packages/config/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"version": "3.2.1",
44
"description": "Shared (build) configuration for jsPsych packages",
55
"type": "module",
6+
"scripts": {
7+
"test": "jest",
8+
"test:watch": "npm test -- --watch"
9+
},
610
"exports": {
711
"./gulp": {
812
"import": "./gulp.js",
@@ -45,6 +49,7 @@
4549
"@sucrase/jest-plugin": "3.0.0",
4650
"@types/gulp": "4.0.17",
4751
"@types/jest": "29.5.8",
52+
"@types/node": "^22.10.10",
4853
"alias-hq": "6.2.4",
4954
"app-root-path": "^3.1.0",
5055
"canvas": "^2.11.2",
@@ -66,7 +71,6 @@
6671
"rollup-plugin-node-externals": "7.1.3",
6772
"sucrase": "3.34.0",
6873
"tslib": "2.6.2",
69-
"typescript": "^5.2.2",
70-
"yaml": "^2.5.1"
74+
"typescript": "^5.2.2"
7175
}
7276
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import fs from "node:fs";
2+
3+
import generateCitations from "../generateCitations";
4+
5+
// Mock filesystem
6+
jest.mock("node:fs");
7+
jest.mock("app-root-path", () => ({
8+
path: "/mock/root/path",
9+
}));
10+
11+
describe("generateCitations", () => {
12+
beforeEach(() => {
13+
jest.clearAllMocks();
14+
});
15+
16+
const validCitationCff = `
17+
cff-version: 1.2.0
18+
message: Please cite this software using these metadata
19+
title: Test Software
20+
authors:
21+
- family-names: Doe
22+
given-names: John
23+
version: 1.0.0
24+
date-released: 2023-01-01
25+
`;
26+
27+
const citationCffWithPreferred = `
28+
cff-version: 1.2.0
29+
message: Please cite this software using these metadata
30+
title: Test Software
31+
authors:
32+
- family-names: Doe
33+
given-names: John
34+
preferred-citation:
35+
title: Preferred Citation
36+
authors:
37+
- family-names: Smith
38+
given-names: Jane
39+
`;
40+
41+
test("should generate citations when CITATION.cff exists in current directory", () => {
42+
fs.readFileSync.mockReturnValue(validCitationCff);
43+
44+
const result = generateCitations();
45+
46+
expect(result).toHaveProperty("apa");
47+
expect(result).toHaveProperty("bibtex");
48+
expect(result.apa).not.toBe("");
49+
expect(result.bibtex).not.toBe("");
50+
});
51+
52+
test("should handle preferred-citation when present", () => {
53+
fs.readFileSync.mockReturnValue(citationCffWithPreferred);
54+
55+
const result = generateCitations();
56+
57+
expect(result).toHaveProperty("apa");
58+
expect(result).toHaveProperty("bibtex");
59+
expect(result.apa.includes("Smith")).toBeTruthy();
60+
});
61+
62+
test("should return empty strings when CITATION.cff is not found", () => {
63+
fs.readFileSync.mockImplementation(() => {
64+
throw new Error("File not found");
65+
});
66+
67+
const result = generateCitations();
68+
69+
expect(result).toEqual({
70+
apa: "",
71+
bibtex: "",
72+
});
73+
});
74+
75+
test("should handle malformed CITATION.cff", () => {
76+
fs.readFileSync.mockReturnValue("invalid: yaml: content:");
77+
78+
const result = generateCitations();
79+
80+
expect(result).toEqual({
81+
apa: "",
82+
bibtex: "",
83+
});
84+
});
85+
86+
test("should remove newlines from citations", () => {
87+
fs.readFileSync.mockReturnValue(validCitationCff);
88+
89+
const result = generateCitations();
90+
91+
expect(result.apa).not.toMatch(/\n/);
92+
expect(result.bibtex).not.toMatch(/\n/);
93+
});
94+
});

packages/jspsych/src/JsPsych.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export class JsPsych {
3636
return version;
3737
}
3838

39+
// prettier-ignore
40+
private citation: any = '__CITATIONS__';
41+
3942
/** Options */
4043
private options: any = {};
4144

@@ -274,8 +277,6 @@ export class JsPsych {
274277
format: "apa" | "bibtex" = "apa"
275278
) {
276279
const formatOptions = ["apa", "bibtex"];
277-
// prettier-ignore
278-
const jsPsychCitations: any = '__CITATIONS__';
279280
format = format.toLowerCase() as "apa" | "bibtex";
280281
// Check if plugins is an array
281282
if (!Array.isArray(plugins)) {
@@ -287,7 +288,7 @@ export class JsPsych {
287288
}
288289
// Print citations
289290
else {
290-
const jsPsychCitation = jsPsychCitations[format];
291+
const jsPsychCitation = this.citation[format];
291292
const citationSet = new Set([jsPsychCitation]);
292293

293294
for (const plugin of plugins) {
Lines changed: 60 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,76 @@
1+
import { initJsPsych } from "../../src";
12
import { TestExtension } from "../extensions/TestExtension";
23
import TestPlugin from "../TestPlugin";
34

4-
const jsPsychApaCitation =
5-
"de Leeuw, J. R., Gilbert, R. A., & Luchterhandt, B. (2023). jsPsych: Enabling an Open-Source Collaborative Ecosystem of Behavioral Experiments. Journal of Open Source Software, 8(85), 5351. https://doi.org/10.21105/joss.05351 ";
6-
const jsPsychBibtexCitation =
7-
'@article{Leeuw2023jsPsych, author = {de Leeuw, Joshua R. and Gilbert, Rebecca A. and Luchterhandt, Bj{\\" o}rn}, journal = {Journal of Open Source Software}, doi = {10.21105/joss.05351}, issn = {2475-9066}, number = {85}, year = {2023}, month = {may 11}, pages = {5351}, publisher = {Open Journals}, title = {jsPsych: Enabling an {Open}-{Source} {Collaborative} {Ecosystem} of {Behavioral} {Experiments}}, url = {https://joss.theoj.org/papers/10.21105/joss.05351}, volume = {8}, } ';
5+
const jsPsychApaCitation = "Test base APA citation";
6+
const jsPsychBibtexCitation = "Test base BibTeX citation";
87
const testPluginApaCitation = "Test plugin APA citation";
98
const testPluginBibtexCitation = "Test plugin BibTeX citation";
109
const testExtensionApaCitation = "Test extension APA citation";
1110

12-
let JsPsych;
11+
let jspsych;
1312

14-
/**
15-
* These tests are skipped if the built version of JsPsych is not found.
16-
* This is because the citation functionality is only available in the built version
17-
* due to code injections that run during the build.
18-
*/
13+
beforeEach(() => {
14+
jspsych = initJsPsych();
15+
(jspsych as any).citation = {
16+
apa: "Test base APA citation",
17+
bibtex: "Test base BibTeX citation",
18+
};
19+
});
1920

20-
try {
21-
// Try to import built version
22-
JsPsych = require("../../dist/index").JsPsych;
23-
let jspsych: typeof JsPsych;
24-
25-
beforeEach(() => {
26-
jspsych = new JsPsych();
21+
describe("citing not using an array", () => {
22+
test("citing without input", () => {
23+
expect(jspsych.getCitations()).toBe(jsPsychApaCitation);
2724
});
28-
29-
describe("citing not using an array", () => {
30-
test("citing without input", () => {
31-
expect(jspsych.getCitations()).toBe(jsPsychApaCitation);
32-
});
33-
test("citing null", () => {
34-
expect(() => jspsych.getCitations(null)).toThrow("Expected array of plugins/extensions");
35-
});
36-
test("citing without input and with invalid format", () => {
37-
expect(() => jspsych.getCitations(null, "apa")).toThrow(
38-
"Expected array of plugins/extensions"
39-
);
40-
});
25+
test("citing null", () => {
26+
expect(() => jspsych.getCitations(null)).toThrow("Expected array of plugins/extensions");
27+
});
28+
test("citing without input and with invalid format", () => {
29+
expect(() => jspsych.getCitations(null, "apa")).toThrow("Expected array of plugins/extensions");
4130
});
31+
});
4232

43-
describe("citing using an array in different formats", () => {
44-
test("citing empty array with APA format", () => {
45-
expect(jspsych.getCitations([], "apa")).toBe(jsPsychApaCitation);
46-
});
47-
test("citing empty array with BibTeX format", () => {
48-
expect(jspsych.getCitations([], "bibtex")).toBe(jsPsychBibtexCitation);
49-
});
50-
test("citing empty array without format", () => {
51-
expect(jspsych.getCitations([])).toBe(jsPsychApaCitation);
52-
});
53-
test("citing one plugin with valid format in all caps", () => {
54-
expect(jspsych.getCitations([TestPlugin], "APA")).toBe(
55-
jsPsychApaCitation + "\n" + testPluginApaCitation
56-
);
57-
});
58-
test("citing with unsupported format", () => {
59-
expect(() => jspsych.getCitations([TestPlugin], "DummyTex")).toThrow(
60-
"Unsupported citation format"
61-
);
62-
});
33+
describe("citing using an array in different formats", () => {
34+
test("citing empty array with APA format", () => {
35+
expect(jspsych.getCitations([], "apa")).toBe(jsPsychApaCitation);
36+
});
37+
test("citing empty array with BibTeX format", () => {
38+
expect(jspsych.getCitations([], "bibtex")).toBe(jsPsychBibtexCitation);
39+
});
40+
test("citing empty array without format", () => {
41+
expect(jspsych.getCitations([])).toBe(jsPsychApaCitation);
6342
});
43+
test("citing one plugin with valid format in all caps", () => {
44+
expect(jspsych.getCitations([TestPlugin], "APA")).toBe(
45+
jsPsychApaCitation + "\n" + testPluginApaCitation
46+
);
47+
});
48+
test("citing with unsupported format", () => {
49+
expect(() => jspsych.getCitations([TestPlugin], "DummyTex")).toThrow(
50+
"Unsupported citation format"
51+
);
52+
});
53+
});
6454

65-
describe("citing mix of valid plugins/extensions", () => {
66-
test("citing a plugin", () => {
67-
expect(jspsych.getCitations([TestPlugin])).toBe(
68-
jsPsychApaCitation + "\n" + testPluginApaCitation
69-
);
70-
});
71-
test("citing a plugin in BibTeX", () => {
72-
expect(jspsych.getCitations([TestPlugin], "bibtex")).toBe(
73-
jsPsychBibtexCitation + "\n" + testPluginBibtexCitation
74-
);
75-
});
76-
test("citing multiple plugins", () => {
77-
expect(jspsych.getCitations([TestPlugin, TestPlugin])).toBe(
78-
jsPsychApaCitation + "\n" + testPluginApaCitation
79-
);
80-
});
81-
test("citing mix of plugins and extensions", () => {
82-
expect(jspsych.getCitations([TestPlugin, TestExtension])).toBe(
83-
jsPsychApaCitation + "\n" + testPluginApaCitation + "\n" + testExtensionApaCitation
84-
);
85-
});
55+
describe("citing mix of valid plugins/extensions", () => {
56+
test("citing a plugin", () => {
57+
expect(jspsych.getCitations([TestPlugin])).toBe(
58+
jsPsychApaCitation + "\n" + testPluginApaCitation
59+
);
60+
});
61+
test("citing a plugin in BibTeX", () => {
62+
expect(jspsych.getCitations([TestPlugin], "bibtex")).toBe(
63+
jsPsychBibtexCitation + "\n" + testPluginBibtexCitation
64+
);
65+
});
66+
test("citing multiple plugins", () => {
67+
expect(jspsych.getCitations([TestPlugin, TestPlugin])).toBe(
68+
jsPsychApaCitation + "\n" + testPluginApaCitation
69+
);
8670
});
87-
} catch (e) {
88-
// Fall back to development version if built version not found
89-
describe("skipping citation tests because of missing built version", () => {
90-
test.skip("skip", () => {
91-
expect(true).toBe(true);
92-
});
71+
test("citing mix of plugins and extensions", () => {
72+
expect(jspsych.getCitations([TestPlugin, TestExtension])).toBe(
73+
jsPsychApaCitation + "\n" + testPluginApaCitation + "\n" + testExtensionApaCitation
74+
);
9375
});
94-
}
76+
});

0 commit comments

Comments
 (0)