mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
Merge branch 'fix/blocks-migration' into fix/cta-logic-templates
This commit is contained in:
@@ -105,7 +105,7 @@ const starRatingSurvey = (t: TFunction): TXMTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -195,7 +195,7 @@ const csatSurvey = (t: TFunction): TXMTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -316,7 +316,7 @@ const smileysRatingSurvey = (t: TFunction): TXMTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
|
||||
@@ -325,7 +325,7 @@ describe("getSurveySummaryDropOff", () => {
|
||||
{
|
||||
id: "c1",
|
||||
leftOperand: {
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
value: "q2",
|
||||
},
|
||||
operator: "equals" as const,
|
||||
|
||||
@@ -227,7 +227,7 @@ export const createBlockJumpLogic = (
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: sourceElementId,
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: operator,
|
||||
},
|
||||
@@ -257,7 +257,7 @@ export const createBlockChoiceJumpLogic = (
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: sourceElementId,
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "equals",
|
||||
rightOperand: {
|
||||
|
||||
@@ -27,7 +27,7 @@ export const createJumpLogic = (
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: sourceQuestionId,
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: operator,
|
||||
},
|
||||
@@ -57,7 +57,7 @@ export const createChoiceJumpLogic = (
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: sourceQuestionId,
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "equals",
|
||||
rightOperand: {
|
||||
|
||||
@@ -1078,7 +1078,7 @@ const reviewPrompt = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -1902,7 +1902,7 @@ const integrationSetupSurvey = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isGreaterThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -2347,7 +2347,7 @@ const collectFeedback = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -2391,7 +2391,7 @@ const collectFeedback = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[1],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isSubmitted",
|
||||
},
|
||||
@@ -3016,7 +3016,7 @@ const rateCheckoutExperience = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isGreaterThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -3111,7 +3111,7 @@ const measureSearchExperience = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isGreaterThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -3206,7 +3206,7 @@ const evaluateContentQuality = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isGreaterThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -3329,7 +3329,7 @@ const measureTaskAccomplishment = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[1],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isGreaterThanOrEqual",
|
||||
rightOperand: {
|
||||
@@ -3372,7 +3372,7 @@ const measureTaskAccomplishment = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[2],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isSubmitted",
|
||||
},
|
||||
@@ -3380,7 +3380,7 @@ const measureTaskAccomplishment = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[1],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isSkipped",
|
||||
},
|
||||
@@ -3419,7 +3419,7 @@ const measureTaskAccomplishment = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[3],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isSubmitted",
|
||||
},
|
||||
@@ -3427,7 +3427,7 @@ const measureTaskAccomplishment = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[1],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isSkipped",
|
||||
},
|
||||
@@ -3534,7 +3534,7 @@ const identifySignUpBarriers = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[1],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "equals",
|
||||
rightOperand: {
|
||||
@@ -3848,7 +3848,7 @@ const improveNewsletterContent = (t: TFunction): TTemplate => {
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableElementIds[0],
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator: "isLessThan",
|
||||
rightOperand: {
|
||||
|
||||
@@ -418,7 +418,7 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
conditions: [
|
||||
{
|
||||
id: "swlje0bsnh6lkyk8vqs13oyr",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "blue" },
|
||||
},
|
||||
@@ -434,13 +434,13 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
conditions: [
|
||||
{
|
||||
id: "n74oght3ozqgwm9rifp2fxrr",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "blue" },
|
||||
},
|
||||
{
|
||||
id: "fg4c9dwt9qjy8aba7zxbfdqd",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "pizza" },
|
||||
},
|
||||
@@ -456,13 +456,13 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
conditions: [
|
||||
{
|
||||
id: "tmj7p9d3kpz1v4mcgpguqytw",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "pizza" },
|
||||
},
|
||||
{
|
||||
id: "rs7v5mmoetff7x8lo1gdsgpr",
|
||||
leftOperand: { type: "question", value: "q3" },
|
||||
leftOperand: { type: "element", value: "q3" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "Inception" },
|
||||
},
|
||||
@@ -480,7 +480,7 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
id: "ddhaccfqy7rr3d5jdswl8yl8",
|
||||
leftOperand: { type: "variable", value: "siog1dabtpo3l0a3xoxw2922" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "question", value: "q4" },
|
||||
rightOperand: { type: "element", value: "q4" },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -502,7 +502,7 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
id: "ot894j7nwna24i6jo2zpk59o",
|
||||
leftOperand: { type: "variable", value: "km1srr55owtn2r7lkoh5ny1u" },
|
||||
operator: "isLessThan",
|
||||
rightOperand: { type: "question", value: "q5" },
|
||||
rightOperand: { type: "element", value: "q5" },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -516,7 +516,7 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
conditions: [
|
||||
{
|
||||
id: "rb223vmzuuzo3ag1bp2m3i69",
|
||||
leftOperand: { type: "question", value: "q6" },
|
||||
leftOperand: { type: "element", value: "q6" },
|
||||
operator: "includesOneOf",
|
||||
rightOperand: {
|
||||
type: "static",
|
||||
@@ -525,7 +525,7 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
},
|
||||
{
|
||||
id: "ot894j7nwna24i6jo2zpk59o",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
operator: "doesNotEqual",
|
||||
rightOperand: { type: "static", value: "teal" },
|
||||
},
|
||||
@@ -535,7 +535,7 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
conditions: [
|
||||
{
|
||||
id: "gy6xowchkv8bp1qj7ur79jvc",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
operator: "doesNotEqual",
|
||||
rightOperand: { type: "static", value: "pizza" },
|
||||
},
|
||||
@@ -543,13 +543,13 @@ export const mockSurveyWithLogic: TSurvey = {
|
||||
id: "vxyccgwsbq34s3l0syom7y2w",
|
||||
leftOperand: { type: "hiddenField", value: "name" },
|
||||
operator: "contains",
|
||||
rightOperand: { type: "question", value: "q2" },
|
||||
rightOperand: { type: "element", value: "q2" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "yunz0k9w0xwparogz2n1twoy",
|
||||
leftOperand: { type: "question", value: "q3" },
|
||||
leftOperand: { type: "element", value: "q3" },
|
||||
operator: "doesNotEqual",
|
||||
rightOperand: { type: "static", value: "Inception" },
|
||||
},
|
||||
|
||||
@@ -448,7 +448,7 @@ describe("surveyLogic", () => {
|
||||
mockSurvey,
|
||||
{},
|
||||
vars,
|
||||
group({ ...baseCond("equals", "foo"), leftOperand: { type: "question", value: "notfound" } }),
|
||||
group({ ...baseCond("equals", "foo"), leftOperand: { type: "element", value: "notfound" } }),
|
||||
"en"
|
||||
)
|
||||
).toBe(false);
|
||||
@@ -854,7 +854,7 @@ describe("surveyLogic", () => {
|
||||
// Test number question
|
||||
const numberCondition: TSingleCondition = {
|
||||
id: "numCond",
|
||||
leftOperand: { type: "question", value: "numQuestion" },
|
||||
leftOperand: { type: "element", value: "numQuestion" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: 42 },
|
||||
};
|
||||
@@ -871,7 +871,7 @@ describe("surveyLogic", () => {
|
||||
// Test MC single with recognized choice
|
||||
const mcSingleCondition: TSingleCondition = {
|
||||
id: "mcCond",
|
||||
leftOperand: { type: "question", value: "mcSingle" },
|
||||
leftOperand: { type: "element", value: "mcSingle" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "choice1" },
|
||||
};
|
||||
@@ -888,7 +888,7 @@ describe("surveyLogic", () => {
|
||||
// Test MC multi
|
||||
const mcMultiCondition: TSingleCondition = {
|
||||
id: "mcMultiCond",
|
||||
leftOperand: { type: "question", value: "mcMulti" },
|
||||
leftOperand: { type: "element", value: "mcMulti" },
|
||||
operator: "includesOneOf",
|
||||
rightOperand: { type: "static", value: ["choice1"] },
|
||||
};
|
||||
@@ -905,7 +905,7 @@ describe("surveyLogic", () => {
|
||||
// Test matrix question
|
||||
const matrixCondition: TSingleCondition = {
|
||||
id: "matrixCond",
|
||||
leftOperand: { type: "question", value: "matrixQ", meta: { row: "0" } },
|
||||
leftOperand: { type: "element", value: "matrixQ", meta: { row: "0" } },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "0" },
|
||||
};
|
||||
@@ -939,7 +939,7 @@ describe("surveyLogic", () => {
|
||||
// Test with missing question
|
||||
const missingQuestionCondition: TSingleCondition = {
|
||||
id: "missingCond",
|
||||
leftOperand: { type: "question", value: "nonExistent" },
|
||||
leftOperand: { type: "element", value: "nonExistent" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "foo" },
|
||||
};
|
||||
@@ -973,7 +973,7 @@ describe("surveyLogic", () => {
|
||||
// Test MC single with "other" option
|
||||
const otherCondition: TSingleCondition = {
|
||||
id: "otherCond",
|
||||
leftOperand: { type: "question", value: "mcSingle" },
|
||||
leftOperand: { type: "element", value: "mcSingle" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "Unknown option" },
|
||||
};
|
||||
@@ -990,7 +990,7 @@ describe("surveyLogic", () => {
|
||||
// Test matrix with invalid row index
|
||||
const invalidMatrixCondition: TSingleCondition = {
|
||||
id: "invalidMatrixCond",
|
||||
leftOperand: { type: "question", value: "matrixQ", meta: { row: "999" } },
|
||||
leftOperand: { type: "element", value: "matrixQ", meta: { row: "999" } },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: "0" },
|
||||
};
|
||||
@@ -1049,7 +1049,7 @@ describe("surveyLogic", () => {
|
||||
id: "questionCond",
|
||||
leftOperand: { type: "hiddenField", value: "f" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "question", value: "question1" },
|
||||
rightOperand: { type: "element", value: "question1" },
|
||||
};
|
||||
|
||||
const variableCondition: TSingleCondition = {
|
||||
@@ -1150,7 +1150,7 @@ describe("surveyLogic", () => {
|
||||
objective: "calculate",
|
||||
variableId: "numVar",
|
||||
operator: "add",
|
||||
value: { type: "question", value: "questionNum" },
|
||||
value: { type: "element", value: "questionNum" },
|
||||
};
|
||||
|
||||
// Test with hidden field value
|
||||
@@ -1168,7 +1168,7 @@ describe("surveyLogic", () => {
|
||||
objective: "calculate",
|
||||
variableId: "textVar",
|
||||
operator: "concat",
|
||||
value: { type: "question", value: "questionText" },
|
||||
value: { type: "element", value: "questionText" },
|
||||
};
|
||||
|
||||
// Test with missing variable
|
||||
@@ -1186,7 +1186,7 @@ describe("surveyLogic", () => {
|
||||
objective: "calculate",
|
||||
variableId: "numVar",
|
||||
operator: "add",
|
||||
value: { type: "question", value: "nonExistentQuestion" },
|
||||
value: { type: "element", value: "nonExistentQuestion" },
|
||||
};
|
||||
|
||||
// Test with other math operations
|
||||
@@ -1348,7 +1348,7 @@ describe("surveyLogic", () => {
|
||||
|
||||
const condition: TSingleCondition = {
|
||||
id: "numCond",
|
||||
leftOperand: { type: "question", value: "numQuestion" },
|
||||
leftOperand: { type: "element", value: "numQuestion" },
|
||||
operator: "equals",
|
||||
rightOperand: { type: "static", value: 0 },
|
||||
};
|
||||
|
||||
@@ -272,7 +272,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
let leftField: TSurveyElement | TSurveyVariable | string;
|
||||
|
||||
if (condition.leftOperand?.type === "question") {
|
||||
if (condition.leftOperand?.type === "element") {
|
||||
leftField = questions.find((q) => q.id === condition.leftOperand?.value) ?? "";
|
||||
} else if (condition.leftOperand?.type === "variable") {
|
||||
leftField = localSurvey.variables.find((v) => v.id === condition.leftOperand?.value) as TSurveyVariable;
|
||||
@@ -284,7 +284,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
let rightField: TSurveyElement | TSurveyVariable | string;
|
||||
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if (condition.rightOperand?.type === "element") {
|
||||
rightField = questions.find((q) => q.id === condition.rightOperand?.value) ?? "";
|
||||
} else if (condition.rightOperand?.type === "variable") {
|
||||
rightField = localSurvey.variables.find(
|
||||
@@ -306,7 +306,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
switch (condition.operator) {
|
||||
case "equals":
|
||||
if (condition.leftOperand.type === "question") {
|
||||
if (condition.leftOperand.type === "element") {
|
||||
if (
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
@@ -318,7 +318,7 @@ const evaluateSingleCondition = (
|
||||
}
|
||||
|
||||
// when left value is of openText, hiddenField, variable and right value is of multichoice
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if (condition.rightOperand?.type === "element") {
|
||||
if ((rightField as TSurveyElement).type === TSurveyElementTypeEnum.MultipleChoiceMulti) {
|
||||
if (Array.isArray(rightValue) && typeof leftValue === "string" && rightValue.length === 1) {
|
||||
return rightValue.includes(leftValue as string);
|
||||
@@ -342,7 +342,7 @@ const evaluateSingleCondition = (
|
||||
case "doesNotEqual":
|
||||
// when left value is of picture selection question and right value is its option
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
condition.leftOperand.type === "element" &&
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.PictureSelection &&
|
||||
Array.isArray(leftValue) &&
|
||||
leftValue.length > 0 &&
|
||||
@@ -353,7 +353,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
// when left value is of date question and right value is string
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
condition.leftOperand.type === "element" &&
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
typeof rightValue === "string"
|
||||
@@ -362,7 +362,7 @@ const evaluateSingleCondition = (
|
||||
}
|
||||
|
||||
// when left value is of openText, hiddenField, variable and right value is of multichoice
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if (condition.rightOperand?.type === "element") {
|
||||
if ((rightField as TSurveyElement).type === TSurveyElementTypeEnum.MultipleChoiceMulti) {
|
||||
if (Array.isArray(rightValue) && typeof leftValue === "string" && rightValue.length === 1) {
|
||||
return !rightValue.includes(leftValue as string);
|
||||
@@ -398,7 +398,7 @@ const evaluateSingleCondition = (
|
||||
case "isSubmitted":
|
||||
if (typeof leftValue === "string") {
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
condition.leftOperand.type === "element" &&
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.FileUpload &&
|
||||
leftValue
|
||||
) {
|
||||
@@ -511,7 +511,7 @@ const getLeftOperandValue = (
|
||||
selectedLanguage: string
|
||||
) => {
|
||||
switch (leftOperand.type) {
|
||||
case "question":
|
||||
case "element":
|
||||
const questions = getElementsFromBlocks(localSurvey.blocks);
|
||||
const currentQuestion = questions.find((q) => q.id === leftOperand.value);
|
||||
if (!currentQuestion) return undefined;
|
||||
@@ -609,7 +609,7 @@ const getRightOperandValue = (
|
||||
if (!rightOperand) return undefined;
|
||||
|
||||
switch (rightOperand.type) {
|
||||
case "question":
|
||||
case "element":
|
||||
return data[rightOperand.value];
|
||||
case "variable":
|
||||
const variables = localSurvey.variables || [];
|
||||
@@ -685,7 +685,7 @@ const performCalculation = (
|
||||
operandValue = value;
|
||||
}
|
||||
break;
|
||||
case "question":
|
||||
case "element":
|
||||
case "hiddenField":
|
||||
const val = data[action.value.value];
|
||||
if (typeof val === "number" || typeof val === "string") {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { TResponseData, TResponseVariables } from "@formbricks/types/responses";
|
||||
import { TSurvey, TSurveyQuestion, TSurveyRecallItem } from "@formbricks/types/surveys/types";
|
||||
import { TSurvey, TSurveyRecallItem } from "@formbricks/types/surveys/types";
|
||||
import { structuredClone } from "@/lib/pollyfills/structuredClone";
|
||||
import {
|
||||
checkForEmptyFallBackValue,
|
||||
@@ -40,7 +40,7 @@ vi.mock("@/lib/utils/datetime", () => ({
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
formatDateWithOrdinal: vi.fn((date) => {
|
||||
formatDateWithOrdinal: vi.fn(() => {
|
||||
return "January 1st, 2023";
|
||||
}),
|
||||
}));
|
||||
@@ -355,10 +355,10 @@ describe("recall utility functions", () => {
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].id).toBe("id1");
|
||||
expect(result[0].label).toBe("Question One");
|
||||
expect(result[0].type).toBe("question");
|
||||
expect(result[0].type).toBe("element");
|
||||
expect(result[1].id).toBe("id2");
|
||||
expect(result[1].label).toBe("Question Two");
|
||||
expect(result[1].type).toBe("question");
|
||||
expect(result[1].type).toBe("element");
|
||||
});
|
||||
|
||||
test("handles hidden fields in recall items", () => {
|
||||
@@ -422,7 +422,7 @@ describe("recall utility functions", () => {
|
||||
describe("headlineToRecall", () => {
|
||||
test("transforms headlines to recall info", () => {
|
||||
const text = "What do you think of @Product?";
|
||||
const recallItems: TSurveyRecallItem[] = [{ id: "product", label: "Product", type: "question" }];
|
||||
const recallItems: TSurveyRecallItem[] = [{ id: "product", label: "Product", type: "element" }];
|
||||
const fallbacks: fallbacks = {
|
||||
product: "our product",
|
||||
};
|
||||
@@ -434,8 +434,8 @@ describe("recall utility functions", () => {
|
||||
test("transforms multiple headlines", () => {
|
||||
const text = "Rate @Product made by @Company";
|
||||
const recallItems: TSurveyRecallItem[] = [
|
||||
{ id: "product", label: "Product", type: "question" },
|
||||
{ id: "company", label: "Company", type: "question" },
|
||||
{ id: "product", label: "Product", type: "element" },
|
||||
{ id: "company", label: "Company", type: "element" },
|
||||
];
|
||||
const fallbacks: fallbacks = {
|
||||
product: "our product",
|
||||
|
||||
@@ -170,7 +170,7 @@ export const getRecallItems = (text: string, survey: TSurvey, languageCode: stri
|
||||
|
||||
const getRecallItemType = () => {
|
||||
if (isHiddenField) return "hiddenField";
|
||||
if (isSurveyQuestion) return "question";
|
||||
if (isSurveyQuestion) return "element";
|
||||
if (isVariable) return "variable";
|
||||
};
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ export const QuotaModal = ({
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
leftOperand: { type: "question", value: firstQuestion?.id },
|
||||
leftOperand: { type: "element", value: firstQuestion?.id },
|
||||
operator: firstQuestion ? getDefaultOperatorForElement(firstQuestion, t) : "equals",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -134,7 +134,7 @@ describe("Quota Evaluation Service", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "c1",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
operator: "isGreaterThanOrEqual",
|
||||
rightOperand: { type: "static", value: 18 },
|
||||
},
|
||||
|
||||
@@ -126,13 +126,13 @@ describe("Quota Utils", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "c1",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
operator: "isGreaterThanOrEqual",
|
||||
rightOperand: { type: "static", value: 18 },
|
||||
},
|
||||
{
|
||||
id: "c2",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: { type: "static", value: 25 },
|
||||
},
|
||||
@@ -155,7 +155,7 @@ describe("Quota Utils", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "c3",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
operator: "isGreaterThan",
|
||||
rightOperand: { type: "static", value: 25 },
|
||||
},
|
||||
|
||||
@@ -124,7 +124,7 @@ export const RecallItemSelect = ({
|
||||
);
|
||||
})
|
||||
.map((question) => {
|
||||
return { id: question.id, label: question.headline[selectedLanguageCode], type: "question" as const };
|
||||
return { id: question.id, label: question.headline[selectedLanguageCode], type: "element" as const };
|
||||
});
|
||||
|
||||
return filteredQuestions;
|
||||
@@ -143,7 +143,7 @@ export const RecallItemSelect = ({
|
||||
|
||||
const getRecallItemIcon = (recallItem: TSurveyRecallItem) => {
|
||||
switch (recallItem.type) {
|
||||
case "question":
|
||||
case "element":
|
||||
const question = questions.find((question) => question.id === recallItem.id);
|
||||
if (question) {
|
||||
return questionIconMapping[question?.type as keyof typeof questionIconMapping];
|
||||
|
||||
@@ -75,7 +75,7 @@ export function ConditionalLogic({
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: firstElement.id,
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
operator,
|
||||
},
|
||||
|
||||
@@ -140,7 +140,7 @@ export const QuestionsView = ({
|
||||
updatedCondition.leftOperand = { ...condition.leftOperand, value: updatedId };
|
||||
}
|
||||
|
||||
if (condition.rightOperand?.type === "question" && condition.rightOperand?.value === compareId) {
|
||||
if (condition.rightOperand?.type === "element" && condition.rightOperand?.value === compareId) {
|
||||
updatedCondition.rightOperand = { ...condition.rightOperand, value: updatedId };
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ vi.mock("@/lib/surveyLogic/utils", () => ({
|
||||
|
||||
vi.mock("@/modules/survey/editor/lib/utils", () => ({
|
||||
getConditionValueOptions: vi.fn().mockReturnValue([
|
||||
{ value: "question1", label: "Question 1", type: "question" },
|
||||
{ value: "question1", label: "Question 1", type: "element" },
|
||||
{ value: "variable1", label: "Variable 1", type: "variable" },
|
||||
]),
|
||||
getConditionOperatorOptions: vi.fn().mockReturnValue([
|
||||
@@ -88,6 +88,7 @@ vi.mock("@/modules/survey/editor/lib/utils", () => ({
|
||||
{ value: "isEmpty", label: "is empty" },
|
||||
]),
|
||||
getDefaultOperatorForQuestion: vi.fn().mockReturnValue("equals"),
|
||||
getDefaultOperatorForElement: vi.fn().mockReturnValue("equals"),
|
||||
}));
|
||||
|
||||
vi.mock("@paralleldrive/cuid2", () => ({
|
||||
@@ -249,7 +250,7 @@ describe("shared-conditions-factory", () => {
|
||||
test("should call getConditionValueOptions with questionIdx", async () => {
|
||||
const paramsWithQuestionIdx = {
|
||||
...defaultParams,
|
||||
questionIdx: 0,
|
||||
blockIdx: 0,
|
||||
};
|
||||
const result = createSharedConditionsFactory(paramsWithQuestionIdx, defaultCallbacks);
|
||||
|
||||
@@ -263,7 +264,7 @@ describe("shared-conditions-factory", () => {
|
||||
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
|
||||
const mockCondition: TSingleCondition = {
|
||||
id: "condition1",
|
||||
leftOperand: { value: "question1", type: "question" },
|
||||
leftOperand: { value: "question1", type: "element" },
|
||||
operator: "equals",
|
||||
};
|
||||
|
||||
@@ -276,12 +277,12 @@ describe("shared-conditions-factory", () => {
|
||||
test("should call getMatchValueProps with questionIdx", async () => {
|
||||
const paramsWithQuestionIdx = {
|
||||
...defaultParams,
|
||||
questionIdx: 0,
|
||||
blockIdx: 0,
|
||||
};
|
||||
const result = createSharedConditionsFactory(paramsWithQuestionIdx, defaultCallbacks);
|
||||
const mockCondition: TSingleCondition = {
|
||||
id: "condition1",
|
||||
leftOperand: { value: "question1", type: "question" },
|
||||
leftOperand: { value: "question1", type: "element" },
|
||||
operator: "equals",
|
||||
};
|
||||
|
||||
@@ -308,7 +309,7 @@ describe("shared-conditions-factory", () => {
|
||||
const result = createSharedConditionsFactory(defaultParams, defaultCallbacks);
|
||||
const mockCondition: TSingleCondition = {
|
||||
id: "condition1",
|
||||
leftOperand: { value: "question1", type: "question" },
|
||||
leftOperand: { value: "question1", type: "element" },
|
||||
operator: "equals",
|
||||
};
|
||||
|
||||
@@ -390,9 +391,9 @@ describe("shared-conditions-factory", () => {
|
||||
const updates = {
|
||||
leftOperand: {
|
||||
value: "matrix-question.row1",
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
meta: {
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -407,7 +408,7 @@ describe("shared-conditions-factory", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "condition1",
|
||||
leftOperand: { value: "matrix-question", type: "question" },
|
||||
leftOperand: { value: "matrix-question", type: "element" },
|
||||
operator: "equals",
|
||||
rightOperand: { value: "x", type: "static" },
|
||||
} as TSingleCondition,
|
||||
@@ -416,7 +417,7 @@ describe("shared-conditions-factory", () => {
|
||||
const updated = updater(structuredClone(initial));
|
||||
expect(updated.conditions[0]).toMatchObject({
|
||||
operator: "isEmpty",
|
||||
leftOperand: { value: "matrix-question", type: "question", meta: { row: "row1" } },
|
||||
leftOperand: { value: "matrix-question", type: "element", meta: { row: "row1" } },
|
||||
rightOperand: undefined,
|
||||
});
|
||||
});
|
||||
@@ -427,9 +428,9 @@ describe("shared-conditions-factory", () => {
|
||||
const updates = {
|
||||
leftOperand: {
|
||||
value: "question1",
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
meta: {
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -445,9 +446,9 @@ describe("shared-conditions-factory", () => {
|
||||
const updates = {
|
||||
leftOperand: {
|
||||
value: "matrix-question",
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
meta: {
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -465,13 +466,13 @@ describe("shared-conditions-factory", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "condition1",
|
||||
leftOperand: { value: "question1", type: "question" },
|
||||
leftOperand: { value: "question1", type: "element" },
|
||||
operator: "equals",
|
||||
rightOperand: { value: "test", type: "static" },
|
||||
},
|
||||
{
|
||||
id: "condition2",
|
||||
leftOperand: { value: "question2", type: "question" },
|
||||
leftOperand: { value: "question2", type: "element" },
|
||||
operator: "doesNotEqual",
|
||||
rightOperand: { value: "test2", type: "static" },
|
||||
},
|
||||
@@ -511,7 +512,7 @@ describe("shared-conditions-factory", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "condition1",
|
||||
leftOperand: { value: "question1", type: "question" },
|
||||
leftOperand: { value: "question1", type: "element" },
|
||||
operator: "equals",
|
||||
rightOperand: { value: "test", type: "static" },
|
||||
},
|
||||
@@ -519,7 +520,7 @@ describe("shared-conditions-factory", () => {
|
||||
id: "condition2",
|
||||
leftOperand: {
|
||||
value: "matrix-question",
|
||||
type: "question",
|
||||
type: "element",
|
||||
meta: { row: "row1" },
|
||||
},
|
||||
operator: "isEmpty",
|
||||
@@ -534,7 +535,7 @@ describe("shared-conditions-factory", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "condition1",
|
||||
leftOperand: { value: "question1", type: "question" },
|
||||
leftOperand: { value: "question1", type: "element" },
|
||||
operator: "equals",
|
||||
rightOperand: { value: "test", type: "static" },
|
||||
},
|
||||
@@ -542,7 +543,7 @@ describe("shared-conditions-factory", () => {
|
||||
id: "condition2",
|
||||
leftOperand: {
|
||||
value: "matrix-question",
|
||||
type: "question",
|
||||
type: "element",
|
||||
meta: { row: "row1" },
|
||||
},
|
||||
operator: "isEmpty",
|
||||
@@ -595,7 +596,7 @@ describe("shared-conditions-factory", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should preserve meta for question type conditions", () => {
|
||||
test("should preserve meta for element type conditions", () => {
|
||||
const genericConditions: TQuotaConditionGroup = {
|
||||
id: "root",
|
||||
connector: "and",
|
||||
@@ -604,7 +605,7 @@ describe("shared-conditions-factory", () => {
|
||||
id: "condition1",
|
||||
leftOperand: {
|
||||
value: "question1",
|
||||
type: "question" as const,
|
||||
type: "element" as const,
|
||||
meta: { row: "row1", column: "col1" },
|
||||
},
|
||||
operator: "equals",
|
||||
@@ -616,7 +617,7 @@ describe("shared-conditions-factory", () => {
|
||||
const result = genericConditionsToQuota(genericConditions);
|
||||
|
||||
expect(result.conditions[0].leftOperand).toHaveProperty("meta");
|
||||
if (result.conditions[0].leftOperand.type === "question") {
|
||||
if (result.conditions[0].leftOperand.type === "element") {
|
||||
expect(result.conditions[0].leftOperand.meta).toEqual({ row: "row1", column: "col1" });
|
||||
}
|
||||
});
|
||||
@@ -651,7 +652,7 @@ describe("shared-conditions-factory", () => {
|
||||
conditions: [
|
||||
{
|
||||
id: "condition1",
|
||||
leftOperand: { value: "question1", type: "question" },
|
||||
leftOperand: { value: "question1", type: "element" },
|
||||
operator: "equals",
|
||||
rightOperand: { value: "test", type: "static" },
|
||||
},
|
||||
|
||||
@@ -61,7 +61,7 @@ export function createSharedConditionsFactory(
|
||||
|
||||
// Handles special update logic for matrix elements, setting appropriate operators and metadata
|
||||
const handleMatrixElementUpdate = (resourceId: string, updates: Partial<TSingleCondition>): boolean => {
|
||||
if (updates.leftOperand && updates.leftOperand.type === "question") {
|
||||
if (updates.leftOperand && updates.leftOperand.type === "element") {
|
||||
const [elementId, rowId] = updates.leftOperand.value.split(".");
|
||||
const element = elements.find((q) => q.id === elementId);
|
||||
|
||||
@@ -73,7 +73,7 @@ export function createSharedConditionsFactory(
|
||||
updateCondition(conditionsCopy, resourceId, {
|
||||
leftOperand: {
|
||||
value: elementId,
|
||||
type: "question",
|
||||
type: "element",
|
||||
meta: {
|
||||
row: rowId,
|
||||
},
|
||||
@@ -112,7 +112,7 @@ export function createSharedConditionsFactory(
|
||||
const defaultOperator = elements.length > 0 ? getDefaultOperatorForElement(elements[0], t) : "equals";
|
||||
const newCondition: TSingleCondition = {
|
||||
id: createId(),
|
||||
leftOperand: { value: defaultLeftOperandValue, type: "question" },
|
||||
leftOperand: { value: defaultLeftOperandValue, type: "element" },
|
||||
operator: defaultOperator,
|
||||
};
|
||||
|
||||
@@ -149,7 +149,7 @@ export function createSharedConditionsFactory(
|
||||
}
|
||||
|
||||
// Check if the operator is correct for the element
|
||||
if (updates.leftOperand?.type === "question" && updates.operator) {
|
||||
if (updates.leftOperand?.type === "element" && updates.operator) {
|
||||
const elementId = updates.leftOperand.value.split(".")[0];
|
||||
const element = elements.find((q) => q.id === elementId);
|
||||
|
||||
@@ -219,7 +219,7 @@ export const genericConditionsToQuota = (genericConditions: TQuotaConditionGroup
|
||||
leftOperand: {
|
||||
type: leftOperand.type,
|
||||
value: leftOperand.value,
|
||||
...(leftOperand.type === "question" && leftOperand.meta && { meta: leftOperand.meta }),
|
||||
...(leftOperand.type === "element" && leftOperand.meta && { meta: leftOperand.meta }),
|
||||
},
|
||||
operator: condition.operator,
|
||||
rightOperand: condition.rightOperand,
|
||||
|
||||
@@ -143,7 +143,7 @@ export const getConditionValueOptions = (
|
||||
label: `${getTextContent(processedLabel.default ?? "")} (${elementHeadline})`,
|
||||
value: `${element.id}.${rowIdx}`,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
rowIdx: rowIdx.toString(),
|
||||
},
|
||||
};
|
||||
@@ -154,7 +154,7 @@ export const getConditionValueOptions = (
|
||||
label: elementHeadline,
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -166,7 +166,7 @@ export const getConditionValueOptions = (
|
||||
label: t("environments.surveys.edit.matrix_all_fields", "All fields"),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -179,7 +179,7 @@ export const getConditionValueOptions = (
|
||||
),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -266,7 +266,7 @@ export const getElementOperatorOptions = (
|
||||
options = getLogicRules(t).question[`openText.${inputType}`].options;
|
||||
} else if (element.type === TSurveyElementTypeEnum.Matrix && condition) {
|
||||
const isMatrixRow =
|
||||
condition.leftOperand.type === "question" && condition.leftOperand?.meta?.row !== undefined;
|
||||
condition.leftOperand.type === "element" && condition.leftOperand?.meta?.row !== undefined;
|
||||
options = getLogicRules(t).question[`matrix${isMatrixRow ? ".row" : ""}`].options;
|
||||
} else {
|
||||
options = getLogicRules(t).question[element.type].options;
|
||||
@@ -289,7 +289,7 @@ export const getDefaultOperatorForElement = (
|
||||
};
|
||||
|
||||
export const getFormatLeftOperandValue = (condition: TSingleCondition, localSurvey: TSurvey): string => {
|
||||
if (condition.leftOperand.type === "question") {
|
||||
if (condition.leftOperand.type === "element") {
|
||||
const questions = getElementsFromBlocks(localSurvey.blocks);
|
||||
const question = questions.find((q) => q.id === condition.leftOperand.value);
|
||||
if (question && question.type === TSurveyElementTypeEnum.Matrix) {
|
||||
@@ -313,7 +313,7 @@ export const getConditionOperatorOptions = (
|
||||
return getLogicRules(t)[`variable.${variableType}`].options;
|
||||
} else if (condition.leftOperand.type === "hiddenField") {
|
||||
return getLogicRules(t).hiddenField.options;
|
||||
} else if (condition.leftOperand.type === "question") {
|
||||
} else if (condition.leftOperand.type === "element") {
|
||||
// Derive questions from blocks
|
||||
const elements = getElementsFromBlocks(localSurvey.blocks);
|
||||
const element = elements.find((question) => {
|
||||
@@ -376,7 +376,7 @@ export const getMatchValueProps = (
|
||||
const selectedElement = elements.find((element) => element.id === condition.leftOperand.value);
|
||||
const selectedVariable = variables.find((variable) => variable.id === condition.leftOperand.value);
|
||||
|
||||
if (condition.leftOperand.type === "question") {
|
||||
if (condition.leftOperand.type === "element") {
|
||||
elements = elements.filter((element) => element.id !== condition.leftOperand.value);
|
||||
} else if (condition.leftOperand.type === "variable") {
|
||||
variables = variables.filter((variable) => variable.id !== condition.leftOperand.value);
|
||||
@@ -384,7 +384,7 @@ export const getMatchValueProps = (
|
||||
hiddenFields = hiddenFields.filter((field) => field !== condition.leftOperand.value);
|
||||
}
|
||||
|
||||
if (condition.leftOperand.type === "question") {
|
||||
if (condition.leftOperand.type === "element") {
|
||||
if (selectedElement?.type === TSurveyElementTypeEnum.OpenText) {
|
||||
const allowedElementTypes = [TSurveyElementTypeEnum.OpenText];
|
||||
|
||||
@@ -412,7 +412,7 @@ export const getMatchValueProps = (
|
||||
),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -629,7 +629,7 @@ export const getMatchValueProps = (
|
||||
),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -727,7 +727,7 @@ export const getMatchValueProps = (
|
||||
label: getTextContent(processedHeadline.default ?? ""),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -802,7 +802,7 @@ export const getMatchValueProps = (
|
||||
label: getTextContent(processedHeadline.default ?? ""),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -883,7 +883,7 @@ export const getMatchValueProps = (
|
||||
label: getTextContent(processedHeadline.default ?? ""),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1126,7 +1126,7 @@ export const getActionValueOptions = (
|
||||
label: getTextContent(processedHeadline.default ?? ""),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1184,7 +1184,7 @@ export const getActionValueOptions = (
|
||||
label: getTextContent(getLocalizedValue(element.headline, "default")),
|
||||
value: element.id,
|
||||
meta: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1236,12 +1236,12 @@ export const getActionValueOptions = (
|
||||
|
||||
const isUsedInLeftOperand = (
|
||||
leftOperand: TLeftOperand,
|
||||
type: "question" | "hiddenField" | "variable",
|
||||
type: "element" | "hiddenField" | "variable",
|
||||
id: string
|
||||
): boolean => {
|
||||
switch (type) {
|
||||
case "question":
|
||||
return leftOperand.type === "question" && leftOperand.value === id;
|
||||
case "element":
|
||||
return leftOperand.type === "element" && leftOperand.value === id;
|
||||
case "hiddenField":
|
||||
return leftOperand.type === "hiddenField" && leftOperand.value === id;
|
||||
case "variable":
|
||||
@@ -1253,12 +1253,12 @@ const isUsedInLeftOperand = (
|
||||
|
||||
const isUsedInRightOperand = (
|
||||
rightOperand: TRightOperand,
|
||||
type: "question" | "hiddenField" | "variable",
|
||||
type: "element" | "hiddenField" | "variable",
|
||||
id: string
|
||||
): boolean => {
|
||||
switch (type) {
|
||||
case "question":
|
||||
return rightOperand.type === "question" && rightOperand.value === id;
|
||||
case "element":
|
||||
return rightOperand.type === "element" && rightOperand.value === id;
|
||||
case "hiddenField":
|
||||
return rightOperand.type === "hiddenField" && rightOperand.value === id;
|
||||
case "variable":
|
||||
@@ -1283,8 +1283,8 @@ export const findQuestionUsedInLogic = (survey: TSurvey, questionId: TSurveyQues
|
||||
} else {
|
||||
// It's a TSingleCondition
|
||||
return (
|
||||
(condition.rightOperand && isUsedInRightOperand(condition.rightOperand, "question", questionId)) ||
|
||||
isUsedInLeftOperand(condition.leftOperand, "question", questionId)
|
||||
(condition.rightOperand && isUsedInRightOperand(condition.rightOperand, "element", questionId)) ||
|
||||
isUsedInLeftOperand(condition.leftOperand, "element", questionId)
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1335,8 +1335,8 @@ export const isUsedInQuota = (
|
||||
if (questionId) {
|
||||
return quota.logic.conditions.some(
|
||||
(condition) =>
|
||||
(condition.rightOperand && isUsedInRightOperand(condition.rightOperand, "question", questionId)) ||
|
||||
isUsedInLeftOperand(condition.leftOperand, "question", questionId)
|
||||
(condition.rightOperand && isUsedInRightOperand(condition.rightOperand, "element", questionId)) ||
|
||||
isUsedInLeftOperand(condition.leftOperand, "element", questionId)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1447,7 +1447,7 @@ export const findOptionUsedInLogic = (
|
||||
};
|
||||
|
||||
const isUsedInOperand = (condition: TSingleCondition): boolean => {
|
||||
if (condition.leftOperand.type === "question" && condition.leftOperand.value === questionId) {
|
||||
if (condition.leftOperand.type === "element" && condition.leftOperand.value === questionId) {
|
||||
if (checkInLeftOperand) {
|
||||
if (condition.leftOperand.meta && Object.entries(condition.leftOperand.meta).length > 0) {
|
||||
const optionIdInMeta = Object.values(condition.leftOperand.meta).some(
|
||||
|
||||
@@ -96,7 +96,7 @@ export class RecallNode extends DecoratorNode<ReactNode> {
|
||||
constructor(payload?: RecallPayload, key?: NodeKey) {
|
||||
super(key);
|
||||
const defaultPayload: RecallPayload = {
|
||||
recallItem: { id: "", label: "", type: "question" },
|
||||
recallItem: { id: "", label: "", type: "element" },
|
||||
fallbackValue: "",
|
||||
};
|
||||
const actualPayload = payload || defaultPayload;
|
||||
|
||||
@@ -20,18 +20,37 @@ interface SurveyQuestion {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Condition {
|
||||
// Single condition type (leaf node)
|
||||
interface SingleCondition {
|
||||
id: string;
|
||||
leftOperand?: { value: string; type: string; meta?: Record<string, unknown> };
|
||||
operator?: string;
|
||||
leftOperand: { value: string; type: string; meta?: Record<string, unknown> };
|
||||
operator: string;
|
||||
rightOperand?: { type: string; value: string | number | string[] };
|
||||
conditions?: Condition[];
|
||||
connector?: string;
|
||||
connector?: undefined; // Single conditions don't have connectors
|
||||
}
|
||||
|
||||
// Condition group type (has nested conditions)
|
||||
interface ConditionGroup {
|
||||
id: string;
|
||||
connector: "and" | "or";
|
||||
conditions: Condition[];
|
||||
}
|
||||
|
||||
// Union type for both
|
||||
type Condition = SingleCondition | ConditionGroup;
|
||||
|
||||
// Type guards
|
||||
const isSingleCondition = (condition: Condition): condition is SingleCondition => {
|
||||
return "leftOperand" in condition && "operator" in condition;
|
||||
};
|
||||
|
||||
const isConditionGroup = (condition: Condition): condition is ConditionGroup => {
|
||||
return "conditions" in condition && "connector" in condition;
|
||||
};
|
||||
|
||||
interface SurveyLogic {
|
||||
id: string;
|
||||
conditions: Condition;
|
||||
conditions: ConditionGroup; // Logic always starts with a condition group
|
||||
actions: LogicAction[];
|
||||
}
|
||||
|
||||
@@ -74,6 +93,7 @@ interface CTAMigrationStats {
|
||||
|
||||
/**
|
||||
* Check if a condition references a CTA element with a specific operator
|
||||
* Can handle both SingleCondition and ConditionGroup
|
||||
*/
|
||||
const conditionReferencesCTA = (
|
||||
condition: Condition | null | undefined,
|
||||
@@ -82,14 +102,19 @@ const conditionReferencesCTA = (
|
||||
): boolean => {
|
||||
if (!condition) return false;
|
||||
|
||||
if (condition.leftOperand?.value === ctaElementId) {
|
||||
if (operator) {
|
||||
return condition.operator === operator;
|
||||
// Check if it's a single condition
|
||||
if (isSingleCondition(condition)) {
|
||||
if (condition.leftOperand.value === ctaElementId) {
|
||||
if (operator) {
|
||||
return condition.operator === operator;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (condition.conditions && Array.isArray(condition.conditions)) {
|
||||
// It's a condition group - check nested conditions
|
||||
if (isConditionGroup(condition)) {
|
||||
return condition.conditions.some((c) => conditionReferencesCTA(c, ctaElementId, operator));
|
||||
}
|
||||
|
||||
@@ -100,23 +125,28 @@ const conditionReferencesCTA = (
|
||||
* Remove conditions that reference a CTA element with specific operators
|
||||
*/
|
||||
const removeCtaConditions = (
|
||||
conditionGroup: Condition,
|
||||
conditionGroup: ConditionGroup,
|
||||
ctaElementId: string,
|
||||
operatorsToRemove: string[]
|
||||
): Condition | null => {
|
||||
if (!conditionGroup.conditions) return conditionGroup;
|
||||
|
||||
): ConditionGroup | null => {
|
||||
const filteredConditions = conditionGroup.conditions.filter((condition) => {
|
||||
if (condition.leftOperand?.value === ctaElementId && condition.operator) {
|
||||
return !operatorsToRemove.includes(condition.operator);
|
||||
// Check if it's a single condition referencing the CTA
|
||||
if (isSingleCondition(condition)) {
|
||||
if (condition.leftOperand.value === ctaElementId) {
|
||||
return !operatorsToRemove.includes(condition.operator);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (condition.conditions && Array.isArray(condition.conditions)) {
|
||||
// It's a condition group - recurse
|
||||
if (isConditionGroup(condition)) {
|
||||
const cleaned = removeCtaConditions(condition, ctaElementId, operatorsToRemove);
|
||||
if (!cleaned?.conditions || cleaned.conditions.length === 0) {
|
||||
if (!cleaned || cleaned.conditions.length === 0) {
|
||||
return false;
|
||||
}
|
||||
// Replace the condition with the cleaned version
|
||||
Object.assign(condition, cleaned);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -333,6 +363,53 @@ const updateLogicFallback = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert logic operand types from "question" to "element" recursively (immutable)
|
||||
* @param condition - Condition or condition group to convert
|
||||
* @returns New condition object with "element" type instead of "question"
|
||||
*/
|
||||
const convertQuestionToElementType = (condition: Condition | null | undefined): Condition | null => {
|
||||
if (!condition) return null;
|
||||
|
||||
// Handle single condition
|
||||
if (isSingleCondition(condition)) {
|
||||
const newCondition: SingleCondition = { ...condition };
|
||||
|
||||
// Update leftOperand if it's of type "question"
|
||||
if (condition.leftOperand.type === "question") {
|
||||
newCondition.leftOperand = {
|
||||
...condition.leftOperand,
|
||||
type: "element",
|
||||
};
|
||||
}
|
||||
|
||||
// Update rightOperand if it exists and is of type "question"
|
||||
if (condition.rightOperand && condition.rightOperand.type === "question") {
|
||||
newCondition.rightOperand = {
|
||||
...condition.rightOperand,
|
||||
type: "element",
|
||||
};
|
||||
}
|
||||
|
||||
return newCondition;
|
||||
}
|
||||
|
||||
// Handle condition group
|
||||
if (isConditionGroup(condition)) {
|
||||
const newConditionGroup: ConditionGroup = {
|
||||
...condition,
|
||||
conditions: condition.conditions.map((nestedCondition) => {
|
||||
const converted = convertQuestionToElementType(nestedCondition);
|
||||
return converted ?? nestedCondition;
|
||||
}),
|
||||
};
|
||||
|
||||
return newConditionGroup;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrate a survey from questions to blocks structure
|
||||
* Each question becomes a block with a single element
|
||||
@@ -384,10 +461,22 @@ const migrateQuestionsSurveyToBlocks = (
|
||||
// Phase 2: Update all logic references
|
||||
for (const block of blocks) {
|
||||
if (block.logic && block.logic.length > 0) {
|
||||
block.logic = block.logic.map((item) => ({
|
||||
...item,
|
||||
actions: updateLogicActions(item.actions, questionIdToBlockId, endingIds),
|
||||
}));
|
||||
block.logic = block.logic.map((item) => {
|
||||
// Convert "question" type to "element" type in conditions (immutably)
|
||||
const updatedConditions = convertQuestionToElementType(item.conditions);
|
||||
|
||||
// Since item.conditions is always a ConditionGroup, the result should be too
|
||||
if (!updatedConditions || !isConditionGroup(updatedConditions)) {
|
||||
// This should never happen, but if it does, keep the original
|
||||
return item;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
conditions: updatedConditions,
|
||||
actions: updateLogicActions(item.actions, questionIdToBlockId, endingIds),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (block.logicFallback) {
|
||||
|
||||
@@ -166,7 +166,7 @@ describe("Survey Logic", () => {
|
||||
const singleCondition: TSingleCondition = {
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test" },
|
||||
};
|
||||
expect(isConditionGroup(singleCondition)).toBe(false);
|
||||
@@ -199,13 +199,13 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test answer" },
|
||||
},
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: 42 },
|
||||
},
|
||||
],
|
||||
@@ -222,13 +222,13 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "wrong answer" },
|
||||
},
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: 42 },
|
||||
},
|
||||
],
|
||||
@@ -245,7 +245,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test answer" },
|
||||
},
|
||||
{
|
||||
@@ -255,7 +255,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: "wrong" },
|
||||
},
|
||||
{
|
||||
@@ -280,13 +280,13 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test answer" },
|
||||
},
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: "wrong value" },
|
||||
},
|
||||
],
|
||||
@@ -303,13 +303,13 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "wrong answer" },
|
||||
},
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: "wrong value" },
|
||||
},
|
||||
],
|
||||
@@ -508,7 +508,7 @@ describe("Survey Logic", () => {
|
||||
objective: "calculate",
|
||||
variableId: "var2",
|
||||
operator: "add",
|
||||
value: { type: "question", value: "q2" },
|
||||
value: { type: "element", value: "q2" },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -609,7 +609,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "contains",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test" },
|
||||
},
|
||||
],
|
||||
@@ -623,7 +623,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "doesNotContain",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "invalid" },
|
||||
},
|
||||
],
|
||||
@@ -639,7 +639,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition3",
|
||||
operator: "startsWith",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test" },
|
||||
},
|
||||
],
|
||||
@@ -655,7 +655,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition4",
|
||||
operator: "doesNotStartWith",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "invalid" },
|
||||
},
|
||||
],
|
||||
@@ -671,7 +671,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition5",
|
||||
operator: "endsWith",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "answer" },
|
||||
},
|
||||
],
|
||||
@@ -685,7 +685,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition6",
|
||||
operator: "doesNotEndWith",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "invalid" },
|
||||
},
|
||||
],
|
||||
@@ -704,7 +704,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "isGreaterThan",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: "30" },
|
||||
},
|
||||
],
|
||||
@@ -720,7 +720,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "isLessThan",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: "50" },
|
||||
},
|
||||
],
|
||||
@@ -734,7 +734,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition3",
|
||||
operator: "isGreaterThanOrEqual",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: "42" },
|
||||
},
|
||||
],
|
||||
@@ -750,7 +750,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition4",
|
||||
operator: "isLessThanOrEqual",
|
||||
leftOperand: { type: "question", value: "q2" },
|
||||
leftOperand: { type: "element", value: "q2" },
|
||||
rightOperand: { type: "static", value: "42" },
|
||||
},
|
||||
],
|
||||
@@ -769,7 +769,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "isAfter",
|
||||
leftOperand: { type: "question", value: "q5" },
|
||||
leftOperand: { type: "element", value: "q5" },
|
||||
rightOperand: { type: "static", value: "2022-12-31" },
|
||||
},
|
||||
],
|
||||
@@ -783,7 +783,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "isBefore",
|
||||
leftOperand: { type: "question", value: "q5" },
|
||||
leftOperand: { type: "element", value: "q5" },
|
||||
rightOperand: { type: "static", value: "2023-01-02" },
|
||||
},
|
||||
],
|
||||
@@ -797,7 +797,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition3",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q5" },
|
||||
leftOperand: { type: "element", value: "q5" },
|
||||
rightOperand: { type: "static", value: "2023-01-01" },
|
||||
},
|
||||
],
|
||||
@@ -816,7 +816,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "includesAllOf",
|
||||
leftOperand: { type: "question", value: "q4" },
|
||||
leftOperand: { type: "element", value: "q4" },
|
||||
rightOperand: { type: "static", value: ["opt1", "opt2"] },
|
||||
},
|
||||
],
|
||||
@@ -832,7 +832,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "includesOneOf",
|
||||
leftOperand: { type: "question", value: "q4" },
|
||||
leftOperand: { type: "element", value: "q4" },
|
||||
rightOperand: { type: "static", value: ["opt1", "Invalid Option"] },
|
||||
},
|
||||
],
|
||||
@@ -848,7 +848,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition3",
|
||||
operator: "doesNotIncludeAllOf",
|
||||
leftOperand: { type: "question", value: "q4" },
|
||||
leftOperand: { type: "element", value: "q4" },
|
||||
rightOperand: { type: "static", value: ["Invalid 1", "Invalid 2"] },
|
||||
},
|
||||
],
|
||||
@@ -864,7 +864,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition4",
|
||||
operator: "doesNotIncludeOneOf",
|
||||
leftOperand: { type: "question", value: "q4" },
|
||||
leftOperand: { type: "element", value: "q4" },
|
||||
rightOperand: { type: "static", value: ["opt3", "Invalid Option"] },
|
||||
},
|
||||
],
|
||||
@@ -883,7 +883,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "isSubmitted",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -898,7 +898,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "isSkipped",
|
||||
leftOperand: { type: "question", value: "emptyField" },
|
||||
leftOperand: { type: "element", value: "emptyField" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -913,7 +913,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition3",
|
||||
operator: "isBooked",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1009,7 +1009,7 @@ describe("Survey Logic", () => {
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
value: "q8",
|
||||
meta: { row: "0" },
|
||||
},
|
||||
@@ -1028,7 +1028,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "isSubmitted",
|
||||
leftOperand: { type: "question", value: "q6" },
|
||||
leftOperand: { type: "element", value: "q6" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1043,7 +1043,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "isSkipped",
|
||||
leftOperand: { type: "question", value: "skippedUpload" },
|
||||
leftOperand: { type: "element", value: "skippedUpload" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1064,7 +1064,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "isPartiallySubmitted",
|
||||
leftOperand: { type: "question", value: "q8" },
|
||||
leftOperand: { type: "element", value: "q8" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1089,7 +1089,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "isCompletelySubmitted",
|
||||
leftOperand: { type: "question", value: "q8" },
|
||||
leftOperand: { type: "element", value: "q8" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1114,7 +1114,7 @@ describe("Survey Logic", () => {
|
||||
id: "condition1",
|
||||
// @ts-ignore - intentionally using invalid operator for test
|
||||
operator: "invalidOperator",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test" },
|
||||
},
|
||||
],
|
||||
@@ -1129,7 +1129,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "nonExistentId" },
|
||||
leftOperand: { type: "element", value: "nonExistentId" },
|
||||
rightOperand: { type: "static", value: "test" },
|
||||
},
|
||||
],
|
||||
@@ -1172,7 +1172,7 @@ describe("Survey Logic", () => {
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
value: "q8",
|
||||
meta: { row: "invalid-row" },
|
||||
},
|
||||
@@ -1192,7 +1192,7 @@ describe("Survey Logic", () => {
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
value: "q8",
|
||||
meta: { row: "99" }, // Invalid row index
|
||||
},
|
||||
@@ -1216,7 +1216,7 @@ describe("Survey Logic", () => {
|
||||
id: "condition1",
|
||||
operator: "isEmpty",
|
||||
leftOperand: {
|
||||
type: "question",
|
||||
type: "element",
|
||||
value: "q8",
|
||||
meta: { row: "0" },
|
||||
},
|
||||
@@ -1236,7 +1236,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "doesNotEqual",
|
||||
leftOperand: { type: "question", value: "q7" },
|
||||
leftOperand: { type: "element", value: "q7" },
|
||||
rightOperand: { type: "static", value: "option2" },
|
||||
},
|
||||
],
|
||||
@@ -1259,8 +1259,8 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "dateQ1" },
|
||||
rightOperand: { type: "question", value: "dateQ2" },
|
||||
leftOperand: { type: "element", value: "dateQ1" },
|
||||
rightOperand: { type: "element", value: "dateQ2" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1305,8 +1305,8 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "doesNotEqual",
|
||||
leftOperand: { type: "question", value: "dateQ1" },
|
||||
rightOperand: { type: "question", value: "dateQ2" },
|
||||
leftOperand: { type: "element", value: "dateQ1" },
|
||||
rightOperand: { type: "element", value: "dateQ2" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1353,7 +1353,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "multiValue" },
|
||||
leftOperand: { type: "element", value: "multiValue" },
|
||||
rightOperand: { type: "static", value: "option1" },
|
||||
},
|
||||
],
|
||||
@@ -1370,8 +1370,8 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
rightOperand: { type: "question", value: "multiQ" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "element", value: "multiQ" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1392,7 +1392,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "isEmpty",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1408,7 +1408,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "isNotEmpty",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1425,7 +1425,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "isAnyOf",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: ["wrong answer", "test answer", "another answer"] },
|
||||
},
|
||||
],
|
||||
@@ -1440,7 +1440,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "isAnyOf",
|
||||
leftOperand: { type: "question", value: "q1" },
|
||||
leftOperand: { type: "element", value: "q1" },
|
||||
rightOperand: { type: "static", value: "test answer" },
|
||||
},
|
||||
],
|
||||
@@ -1482,7 +1482,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition1",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "multiChoiceWithOther" },
|
||||
leftOperand: { type: "element", value: "multiChoiceWithOther" },
|
||||
rightOperand: { type: "static", value: "Custom Option" },
|
||||
},
|
||||
],
|
||||
@@ -1503,7 +1503,7 @@ describe("Survey Logic", () => {
|
||||
{
|
||||
id: "condition2",
|
||||
operator: "equals",
|
||||
leftOperand: { type: "question", value: "multiChoiceWithOther" },
|
||||
leftOperand: { type: "element", value: "multiChoiceWithOther" },
|
||||
rightOperand: { type: "static", value: "opt1" },
|
||||
},
|
||||
],
|
||||
|
||||
@@ -88,7 +88,7 @@ const getLeftOperandValue = (
|
||||
selectedLanguage: string
|
||||
) => {
|
||||
switch (leftOperand.type) {
|
||||
case "question":
|
||||
case "element":
|
||||
const questions = getElementsFromSurvey(localSurvey);
|
||||
const currentQuestion = questions.find((q) => q.id === leftOperand.value);
|
||||
if (!currentQuestion) return undefined;
|
||||
@@ -188,7 +188,7 @@ const getRightOperandValue = (
|
||||
if (!rightOperand) return undefined;
|
||||
|
||||
switch (rightOperand.type) {
|
||||
case "question":
|
||||
case "element":
|
||||
return data[rightOperand.value];
|
||||
case "variable":
|
||||
const variables = localSurvey.variables || [];
|
||||
@@ -224,7 +224,7 @@ const evaluateSingleCondition = (
|
||||
let leftField: TSurveyElement | TSurveyVariable | string;
|
||||
|
||||
const questions = getElementsFromSurvey(localSurvey);
|
||||
if (condition.leftOperand?.type === "question") {
|
||||
if (condition.leftOperand?.type === "element") {
|
||||
leftField = questions.find((q) => q.id === condition.leftOperand?.value) ?? "";
|
||||
} else if (condition.leftOperand?.type === "variable") {
|
||||
leftField = localSurvey.variables.find((v) => v.id === condition.leftOperand?.value) as TSurveyVariable;
|
||||
@@ -236,7 +236,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
let rightField: TSurveyElement | TSurveyVariable | string;
|
||||
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if (condition.rightOperand?.type === "element") {
|
||||
rightField = questions.find((q) => q.id === condition.rightOperand?.value) ?? "";
|
||||
} else if (condition.rightOperand?.type === "variable") {
|
||||
rightField = localSurvey.variables.find(
|
||||
@@ -258,7 +258,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
switch (condition.operator) {
|
||||
case "equals":
|
||||
if (condition.leftOperand.type === "question") {
|
||||
if (condition.leftOperand.type === "element") {
|
||||
if (
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
@@ -270,7 +270,7 @@ const evaluateSingleCondition = (
|
||||
}
|
||||
|
||||
// when left value is of openText, hiddenField, variable and right value is of multichoice
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if (condition.rightOperand?.type === "element") {
|
||||
if ((rightField as TSurveyElement).type === TSurveyElementTypeEnum.MultipleChoiceMulti) {
|
||||
if (Array.isArray(rightValue) && typeof leftValue === "string" && rightValue.length === 1) {
|
||||
return rightValue.includes(leftValue as string);
|
||||
@@ -294,7 +294,7 @@ const evaluateSingleCondition = (
|
||||
case "doesNotEqual":
|
||||
// when left value is of picture selection question and right value is its option
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
condition.leftOperand.type === "element" &&
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.PictureSelection &&
|
||||
Array.isArray(leftValue) &&
|
||||
leftValue.length > 0 &&
|
||||
@@ -305,7 +305,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
// when left value is of date question and right value is string
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
condition.leftOperand.type === "element" &&
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
typeof rightValue === "string"
|
||||
@@ -314,7 +314,7 @@ const evaluateSingleCondition = (
|
||||
}
|
||||
|
||||
// when left value is of openText, hiddenField, variable and right value is of multichoice
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if (condition.rightOperand?.type === "element") {
|
||||
if ((rightField as TSurveyElement).type === TSurveyElementTypeEnum.MultipleChoiceMulti) {
|
||||
if (Array.isArray(rightValue) && typeof leftValue === "string" && rightValue.length === 1) {
|
||||
return !rightValue.includes(leftValue as string);
|
||||
@@ -350,7 +350,7 @@ const evaluateSingleCondition = (
|
||||
case "isSubmitted":
|
||||
if (typeof leftValue === "string") {
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
condition.leftOperand.type === "element" &&
|
||||
(leftField as TSurveyElement).type === TSurveyElementTypeEnum.FileUpload &&
|
||||
leftValue
|
||||
) {
|
||||
@@ -474,7 +474,7 @@ const performCalculation = (
|
||||
operandValue = value;
|
||||
}
|
||||
break;
|
||||
case "question":
|
||||
case "element":
|
||||
case "hiddenField":
|
||||
const val = data[action.value.value];
|
||||
if (typeof val === "number" || typeof val === "string") {
|
||||
|
||||
@@ -57,9 +57,9 @@ export const ZConnector = z.enum(["and", "or"]);
|
||||
export type TConnector = z.infer<typeof ZConnector>;
|
||||
|
||||
// Dynamic field types for conditions
|
||||
const ZDynamicQuestion = z.object({
|
||||
type: z.literal("question"),
|
||||
value: z.string().min(1, "Conditional Logic: Question id cannot be empty"),
|
||||
const ZDynamicElement = z.object({
|
||||
type: z.literal("element"),
|
||||
value: z.string().min(1, "Conditional Logic: Element id cannot be empty"),
|
||||
meta: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ const ZDynamicHiddenField = z.object({
|
||||
value: z.string().min(1, "Conditional Logic: Hidden field id cannot be empty"),
|
||||
});
|
||||
|
||||
export const ZDynamicLogicFieldValue = z.union([ZDynamicQuestion, ZDynamicVariable, ZDynamicHiddenField], {
|
||||
export const ZDynamicLogicFieldValue = z.union([ZDynamicElement, ZDynamicVariable, ZDynamicHiddenField], {
|
||||
message: "Conditional Logic: Invalid dynamic field value",
|
||||
});
|
||||
|
||||
|
||||
@@ -2000,7 +2000,7 @@ const validateConditions = (
|
||||
const { leftOperand, operator, rightOperand } = condition;
|
||||
|
||||
// Validate left operand
|
||||
if (leftOperand.type === "question") {
|
||||
if (leftOperand.type === "element") {
|
||||
const questionId = leftOperand.value;
|
||||
const questionIdx = survey.questions.findIndex((q) => q.id === questionId);
|
||||
const question = questionIdx !== -1 ? survey.questions[questionIdx] : undefined;
|
||||
@@ -2057,7 +2057,7 @@ const validateConditions = (
|
||||
|
||||
if (question.type === TSurveyQuestionTypeEnum.OpenText) {
|
||||
// Validate right operand
|
||||
if (rightOperand?.type === "question") {
|
||||
if (rightOperand?.type === "element") {
|
||||
const quesId = rightOperand.value;
|
||||
const ques = survey.questions.find((q) => q.id === quesId);
|
||||
|
||||
@@ -2289,7 +2289,7 @@ const validateConditions = (
|
||||
});
|
||||
}
|
||||
} else if (question.type === TSurveyQuestionTypeEnum.Date) {
|
||||
if (rightOperand?.type === "question") {
|
||||
if (rightOperand?.type === "element") {
|
||||
const quesId = rightOperand.value;
|
||||
const ques = survey.questions.find((q) => q.id === quesId);
|
||||
|
||||
@@ -2419,7 +2419,7 @@ const validateConditions = (
|
||||
}
|
||||
|
||||
// Validate right operand
|
||||
if (rightOperand?.type === "question") {
|
||||
if (rightOperand?.type === "element") {
|
||||
const questionId = rightOperand.value;
|
||||
const question = survey.questions.find((q) => q.id === questionId);
|
||||
|
||||
@@ -2516,7 +2516,7 @@ const validateConditions = (
|
||||
}
|
||||
|
||||
// Validate right operand
|
||||
if (rightOperand?.type === "question") {
|
||||
if (rightOperand?.type === "element") {
|
||||
const questionId = rightOperand.value;
|
||||
const question = survey.questions.find((q) => q.id === questionId);
|
||||
|
||||
@@ -2638,7 +2638,7 @@ const validateActions = (
|
||||
};
|
||||
}
|
||||
|
||||
if (action.value.type === "question") {
|
||||
if (action.value.type === "element") {
|
||||
const allowedQuestions = [
|
||||
TSurveyQuestionTypeEnum.OpenText,
|
||||
TSurveyQuestionTypeEnum.MultipleChoiceSingle,
|
||||
@@ -2670,7 +2670,7 @@ const validateActions = (
|
||||
};
|
||||
}
|
||||
|
||||
if (action.value.type === "question") {
|
||||
if (action.value.type === "element") {
|
||||
const allowedQuestions = [TSurveyQuestionTypeEnum.Rating, TSurveyQuestionTypeEnum.NPS];
|
||||
|
||||
const selectedQuestion = previousQuestions.find((q) => q.id === action.value.value);
|
||||
@@ -2957,7 +2957,7 @@ const validateBlockConditions = (
|
||||
const { leftOperand, operator, rightOperand } = condition;
|
||||
|
||||
// Validate left operand
|
||||
if (leftOperand.type === "question") {
|
||||
if (leftOperand.type === "element") {
|
||||
const elementId = leftOperand.value;
|
||||
const elementInfo = allElements.get(elementId);
|
||||
|
||||
@@ -3026,7 +3026,7 @@ const validateBlockConditions = (
|
||||
|
||||
if (element.type === TSurveyElementTypeEnum.OpenText) {
|
||||
// Validate right operand
|
||||
if (rightOperand?.type === "question") {
|
||||
if (rightOperand?.type === "element") {
|
||||
const elemId = rightOperand.value;
|
||||
const elem = allElements.get(elemId);
|
||||
|
||||
@@ -3274,7 +3274,7 @@ const validateBlockActions = (
|
||||
}
|
||||
|
||||
if (variable.type === "text") {
|
||||
if (action.value.type === "question") {
|
||||
if (action.value.type === "element") {
|
||||
const allowedElements = [
|
||||
TSurveyElementTypeEnum.OpenText,
|
||||
TSurveyElementTypeEnum.MultipleChoiceSingle,
|
||||
@@ -3297,7 +3297,7 @@ const validateBlockActions = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (action.value.type === "question") {
|
||||
if (action.value.type === "element") {
|
||||
const allowedElements = [TSurveyElementTypeEnum.Rating, TSurveyElementTypeEnum.NPS];
|
||||
|
||||
const selectedElement = allElements.get(action.value.value);
|
||||
@@ -3980,7 +3980,7 @@ export type TSortOption = z.infer<typeof ZSortOption>;
|
||||
export const ZSurveyRecallItem = z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
type: z.enum(["question", "hiddenField", "attributeClass", "variable"]),
|
||||
type: z.enum(["element", "hiddenField", "attributeClass", "variable"]),
|
||||
});
|
||||
|
||||
export type TSurveyRecallItem = z.infer<typeof ZSurveyRecallItem>;
|
||||
|
||||
Reference in New Issue
Block a user