diff --git a/.changeset/khaki-bobcats-pump.md b/.changeset/khaki-bobcats-pump.md new file mode 100644 index 0000000..fd11813 --- /dev/null +++ b/.changeset/khaki-bobcats-pump.md @@ -0,0 +1,5 @@ +--- +"@lookit/surveys": patch +--- + +Set SurveyJS Locale to trial's locale parameter. diff --git a/package-lock.json b/package-lock.json index 22db1e3..ffbb3a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11385,10 +11385,6 @@ "node": ">=10" } }, - "node_modules/jquery": { - "version": "3.7.1", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "dev": true, @@ -19193,13 +19189,6 @@ "version": "1.11.14", "license": "MIT" }, - "node_modules/survey-jquery": { - "version": "1.11.14", - "license": "MIT", - "dependencies": { - "jquery": ">=1.12.4" - } - }, "node_modules/survey-knockout-ui": { "version": "1.11.14", "license": "MIT", @@ -21018,8 +21007,7 @@ "dependencies": { "@jspsych/plugin-survey": "^2.0.0", "dompurify": "^3.0.11", - "marked": "^12.0.1", - "survey-jquery": "^1.9.136" + "marked": "^12.0.1" }, "devDependencies": { "@jspsych/config": "^2.0.0", diff --git a/packages/surveys/package.json b/packages/surveys/package.json index 3a17571..7b5e7c0 100644 --- a/packages/surveys/package.json +++ b/packages/surveys/package.json @@ -27,8 +27,7 @@ "dependencies": { "@jspsych/plugin-survey": "^2.0.0", "dompurify": "^3.0.11", - "marked": "^12.0.1", - "survey-jquery": "^1.9.136" + "marked": "^12.0.1" }, "devDependencies": { "@jspsych/config": "^2.0.0", diff --git a/packages/surveys/src/consentSurvey.ts b/packages/surveys/src/consentSurvey.ts index b3cd506..c9eeea8 100644 --- a/packages/surveys/src/consentSurvey.ts +++ b/packages/surveys/src/consentSurvey.ts @@ -1,8 +1,19 @@ import SurveyPlugin from "@jspsych/plugin-survey"; -import { TrialType } from "jspsych"; +import { ParameterType, TrialType } from "jspsych"; import { consentSurveyFunction } from "./utils"; -type Info = typeof SurveyPlugin.info; +const info = { + ...SurveyPlugin.info, + parameters: { + ...SurveyPlugin.info.parameters, + locale: { + type: ParameterType.STRING, + default: "en-us", + }, + }, +}; + +type Info = typeof info; export type Trial = TrialType; /** Consent Survey plugin extends jsPsych's Survey Plugin. */ @@ -18,7 +29,7 @@ export class ConsentSurveyPlugin extends SurveyPlugin { public trial(display_element: HTMLElement, trial: Trial) { super.trial(display_element, { ...trial, - survey_function: consentSurveyFunction(trial.survey_function), + survey_function: consentSurveyFunction(trial), }); } /** diff --git a/packages/surveys/src/errors.ts b/packages/surveys/src/errors.ts new file mode 100644 index 0000000..14294f4 --- /dev/null +++ b/packages/surveys/src/errors.ts @@ -0,0 +1,7 @@ +/** Error thrown when trial is expecting locale parameter and on is not found. */ +export class TrialLocaleParameterUnset extends Error { + /** This will show when the locale is not set in a trial. */ + public constructor() { + super("Locale not set in trial parameters."); + } +} diff --git a/packages/surveys/src/exitSurvey.ts b/packages/surveys/src/exitSurvey.ts index cbc7b11..ad37209 100644 --- a/packages/surveys/src/exitSurvey.ts +++ b/packages/surveys/src/exitSurvey.ts @@ -1,7 +1,7 @@ import SurveyPlugin from "@jspsych/plugin-survey"; import chsTemplates from "@lookit/templates"; import { ParameterType, TrialType } from "jspsych"; -import { exitSurveyFunction as survey_function } from "./utils"; +import { exitSurveyFunction } from "./utils"; const info = { ...SurveyPlugin.info, @@ -48,11 +48,11 @@ export class ExitSurveyPlugin extends SurveyPlugin { * @param display_element - Display element. * @param trial - Info parameters. */ - public trial(display_element: HTMLElement, trial: TrialType) { + public trial(display_element: HTMLElement, trial: Trial) { super.trial(display_element, { ...trial, survey_json: chsTemplates.exitSurvey(trial), - survey_function, + survey_function: exitSurveyFunction(trial), }); } diff --git a/packages/surveys/src/utils.spec.ts b/packages/surveys/src/utils.spec.ts index 7e08189..f56f8c4 100644 --- a/packages/surveys/src/utils.spec.ts +++ b/packages/surveys/src/utils.spec.ts @@ -1,4 +1,6 @@ import { Model } from "survey-jquery"; +import { TrialLocaleParameterUnset } from "./errors"; +import { Trial } from "./exitSurvey"; import { consentSurveyFunction, exitSurveyFunction, @@ -10,9 +12,35 @@ jest.mock("@lookit/data", () => ({ updateResponse: jest.fn().mockReturnValue("Response"), })); +/** + * Helper function to generate a trial object. + * + * @param values - Additonal paramters added to trial object + * @returns Trial object + */ +const getTrial = (values: Record = {}) => + ({ + locale: "en-US", + survey_function: jest.fn(), + ...values, + }) as unknown as Trial; + +/** + * Helper function to generate surveys for testing. + * + * @param values - Values to add to survey object + * @returns Survey + */ +const getSurvey = (values: Record = {}) => + ({ + onComplete: { add: jest.fn() }, + onTextMarkdown: { add: jest.fn() }, + ...values, + }) as unknown as Model; + test("Markdown to HTML through survey function", () => { const addMock = jest.fn(); - const survey = { onTextMarkdown: { add: addMock } } as unknown as Model; + const survey = getSurvey({ onTextMarkdown: { add: addMock } }); const textValue = "some text"; const options = { text: `**${textValue}**`, html: null }; const rtnSurvey = textMarkdownSurveyFunction(survey); @@ -26,11 +54,9 @@ test("Markdown to HTML through survey function", () => { }); test("Exit survey function", () => { - const survey = { - onComplete: { add: jest.fn() }, - onTextMarkdown: { add: jest.fn() }, - } as unknown as Model; - const rtnSurvey = exitSurveyFunction(survey); + const survey = getSurvey(); + const trial = getTrial(); + const rtnSurvey = exitSurveyFunction(trial)(survey); expect(survey.onComplete.add).toHaveBeenCalledTimes(1); expect(survey.onTextMarkdown.add).toHaveBeenCalledTimes(1); @@ -39,13 +65,11 @@ test("Exit survey function", () => { test("Anonymous function within exit survey function where withdrawal > 0", () => { const addMock = jest.fn(); - const survey = { - onComplete: { add: addMock }, - onTextMarkdown: { add: jest.fn() }, - } as unknown as Model; + const survey = getSurvey({ onComplete: { add: addMock } }); const sender = { setValue: jest.fn() }; + const trial = getTrial(); - exitSurveyFunction(survey); + exitSurveyFunction(trial)(survey); const anonFn = addMock.mock.calls[0][0]; @@ -58,13 +82,11 @@ test("Anonymous function within exit survey function where withdrawal > 0", () = test("Anonymous function within exit survey function where withdrawal is 0", () => { const addMock = jest.fn(); - const survey = { - onComplete: { add: addMock }, - onTextMarkdown: { add: jest.fn() }, - } as unknown as Model; + const survey = getSurvey({ onComplete: { add: addMock } }); const sender = { setValue: jest.fn() }; + const trial = getTrial(); - exitSurveyFunction(survey); + exitSurveyFunction(trial)(survey); const anonFn = addMock.mock.calls[0][0]; @@ -77,11 +99,9 @@ test("Anonymous function within exit survey function where withdrawal is 0", () }); test("Consent survey function", () => { - const survey = { - onComplete: { add: jest.fn() }, - onTextMarkdown: { add: jest.fn() }, - } as unknown as Model; - const survey_function = consentSurveyFunction(); + const survey = getSurvey(); + const trial = getTrial(); + const survey_function = consentSurveyFunction(trial); const rtnSurvey = survey_function(survey); expect(survey.onComplete.add).toHaveBeenCalledTimes(1); @@ -90,26 +110,21 @@ test("Consent survey function", () => { }); test("User function for consent survey function", () => { - const survey = { - onComplete: { add: jest.fn() }, - onTextMarkdown: { add: jest.fn() }, - } as unknown as Model; - const userFn = jest.fn(); - const survey_function = consentSurveyFunction(userFn); + const survey = getSurvey(); + const trial = getTrial(); + const survey_function = consentSurveyFunction(trial); survey_function(survey); - expect(userFn).toHaveBeenCalledTimes(1); + expect(trial.survey_function).toHaveBeenCalledTimes(1); }); test("Anonymous function within consent survey function", () => { const addMock = jest.fn(); - const survey = { - onComplete: { add: addMock }, - onTextMarkdown: { add: jest.fn() }, - } as unknown as Model; + const survey = getSurvey({ onComplete: { add: addMock } }); + const trial = getTrial(); - consentSurveyFunction()(survey); + consentSurveyFunction(trial)(survey); const anonFn = addMock.mock.calls[0][0]; @@ -120,3 +135,25 @@ test("Anonymous function within consent survey function", () => { expect(survey.onComplete.add).toHaveBeenCalledTimes(1); expect(survey.onTextMarkdown.add).toHaveBeenCalledTimes(1); }); + +test("Set SurveyJS locale parameter", () => { + const trial = getTrial(); + const survey = getSurvey(); + exitSurveyFunction(trial)(survey); + expect(survey.locale).toStrictEqual("en-US"); +}); + +test("Set SurveyJS locale parameter to French", () => { + const trial = getTrial({ locale: "fr" }); + const survey = getSurvey(); + exitSurveyFunction(trial)(survey); + expect(survey.locale).toStrictEqual(trial.locale); +}); + +test("Survey will throw error if locale is not set", () => { + const trial = getTrial({ locale: undefined }); + const survey = getSurvey(); + expect(() => exitSurveyFunction(trial)(survey)).toThrow( + TrialLocaleParameterUnset, + ); +}); diff --git a/packages/surveys/src/utils.ts b/packages/surveys/src/utils.ts index 91fb363..d334303 100644 --- a/packages/surveys/src/utils.ts +++ b/packages/surveys/src/utils.ts @@ -2,10 +2,16 @@ import Data from "@lookit/data"; import { LookitWindow } from "@lookit/data/dist/types"; import DOMPurify from "dompurify"; import { marked } from "marked"; -import { Model } from "survey-jquery"; +import { Model } from "survey-core"; +import "survey-core/survey.i18n"; +import { Trial as ConsentSurveyTrial } from "./consentSurvey"; +import { TrialLocaleParameterUnset } from "./errors"; +import { Trial as ExitSurveyTrial } from "./exitSurvey"; declare let window: LookitWindow; +type LocaleTrial = ConsentSurveyTrial | ExitSurveyTrial; + const CONFIG = { marked: { async: false }, dompurify: { USE_PROFILES: { html: true } }, @@ -28,6 +34,19 @@ export const textMarkdownSurveyFunction = (survey: Model) => { return survey; }; +/** + * Set locale parameter on SurveyJS Model. + * + * @param survey - SurveyJS model + * @param trial - Trial data including user supplied parameters. + */ +const setSurveyLocale = (survey: Model, trial: LocaleTrial) => { + if (!trial.locale) { + throw new TrialLocaleParameterUnset(); + } + survey.locale = new Intl.Locale(trial.locale).baseName; +}; + /** * Survey function used in exit survey. Adds markdown support through * "textMarkdownSurveyFunction". For the withdrawal checkbox question, this @@ -36,19 +55,22 @@ export const textMarkdownSurveyFunction = (survey: Model) => { * question type rather than boolean with "renderAs: checkbox" because the * latter doesn't allow both a question title and label next to the checkbox. * - * @param survey - Survey model provided by SurveyJS. + * @param trial - Trial data including user supplied parameters. * @returns Survey model. */ -export const exitSurveyFunction = (survey: Model) => { - textMarkdownSurveyFunction(survey); +export const exitSurveyFunction = + (trial: ExitSurveyTrial) => (survey: Model) => { + setSurveyLocale(survey, trial); + textMarkdownSurveyFunction(survey); - survey.onComplete.add((sender) => { - const trueFalseValue = - sender.getQuestionByName("withdrawal").value.length > 0; - sender.setValue("withdrawal", trueFalseValue); - }); - return survey; -}; + survey.onComplete.add((sender) => { + const trueFalseValue = + sender.getQuestionByName("withdrawal").value.length > 0; + sender.setValue("withdrawal", trueFalseValue); + }); + + return survey; + }; /** * Survey function used by Consent Survey. Adds markdown support through @@ -56,11 +78,12 @@ export const exitSurveyFunction = (survey: Model) => { * that consent was completed, and that the consent was through a survey (rather * than video). * - * @param userfn - Survey function provided by user. + * @param trial - Trial data including user supplied parameters. * @returns Survey model. */ -export const consentSurveyFunction = (userfn?: (x: Model) => Model) => { - return function (survey: Model) { +export const consentSurveyFunction = + (trial: ConsentSurveyTrial) => (survey: Model) => { + setSurveyLocale(survey, trial); textMarkdownSurveyFunction(survey); survey.onComplete.add(async () => { @@ -70,10 +93,9 @@ export const consentSurveyFunction = (userfn?: (x: Model) => Model) => { }); }); - if (typeof userfn === "function") { - userfn(survey); + if (typeof trial.survey_function === "function") { + trial.survey_function(survey); } return survey; }; -}; diff --git a/packages/templates/src/exitSurveyTemplate.ts b/packages/templates/src/exitSurveyTemplate.ts index b7f8f58..7d16131 100644 --- a/packages/templates/src/exitSurveyTemplate.ts +++ b/packages/templates/src/exitSurveyTemplate.ts @@ -141,7 +141,6 @@ class ExitSurveyJson { /** Translate the survey text. */ private translation() { - console.log(this.survey.pages[0].elements[0]); const { contact_info, name } = window.chs.study.attributes; const view = { ...this.trial,