Skip to content

Commit

Permalink
SurveyJS locale (#98)
Browse files Browse the repository at this point in the history
* Remove unneeded survey-jquery package

* Set surveyJS locale

* Update plugin with new survey function

* Remove print statement

* Add tests for SurveyJS locale

* Changeset

* Add locale to consent survey

* Update tests
  • Loading branch information
okaycj authored Nov 4, 2024
1 parent 599cfce commit 0bcc29a
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 72 deletions.
5 changes: 5 additions & 0 deletions .changeset/khaki-bobcats-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lookit/surveys": patch
---

Set SurveyJS Locale to trial's locale parameter.
14 changes: 1 addition & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions packages/surveys/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 14 additions & 3 deletions packages/surveys/src/consentSurvey.ts
Original file line number Diff line number Diff line change
@@ -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 = <const>{
...SurveyPlugin.info,
parameters: {
...SurveyPlugin.info.parameters,
locale: {
type: ParameterType.STRING,
default: "en-us",
},
},
};

type Info = typeof info;
export type Trial = TrialType<Info>;

/** Consent Survey plugin extends jsPsych's Survey Plugin. */
Expand All @@ -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),
});
}
/**
Expand Down
7 changes: 7 additions & 0 deletions packages/surveys/src/errors.ts
Original file line number Diff line number Diff line change
@@ -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.");
}
}
6 changes: 3 additions & 3 deletions packages/surveys/src/exitSurvey.ts
Original file line number Diff line number Diff line change
@@ -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 = <const>{
...SurveyPlugin.info,
Expand Down Expand Up @@ -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<Info>) {
public trial(display_element: HTMLElement, trial: Trial) {
super.trial(display_element, {
...trial,
survey_json: chsTemplates.exitSurvey(trial),
survey_function,
survey_function: exitSurveyFunction(trial),
});
}

Expand Down
103 changes: 70 additions & 33 deletions packages/surveys/src/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Model } from "survey-jquery";
import { TrialLocaleParameterUnset } from "./errors";
import { Trial } from "./exitSurvey";
import {
consentSurveyFunction,
exitSurveyFunction,
Expand All @@ -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<string, string | boolean | undefined> = {}) =>
({
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<string, string | object> = {}) =>
({
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);
Expand All @@ -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);
Expand All @@ -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];

Expand All @@ -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];

Expand All @@ -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);
Expand All @@ -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];

Expand All @@ -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,
);
});
56 changes: 39 additions & 17 deletions packages/surveys/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <const>{
marked: { async: false },
dompurify: { USE_PROFILES: { html: true } },
Expand All @@ -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
Expand All @@ -36,31 +55,35 @@ 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
* "textMarkdownSurveyFunction". On complete, this will mark in the Response
* 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 () => {
Expand All @@ -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;
};
};
1 change: 0 additions & 1 deletion packages/templates/src/exitSurveyTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 0bcc29a

Please sign in to comment.