From c29016080b170016ef6bfcf5e297f9cd63efbc10 Mon Sep 17 00:00:00 2001 From: Andrew Telnov Date: Tue, 25 Jun 2024 15:44:51 +0300 Subject: [PATCH 1/3] [React] Carry Forward Choices - Uncaught TypeError: Cannot read properties of undefined (reading 'isEnabled') fix #8462 --- src/react/reactquestion_ranking.tsx | 68 +++++++++++++++-------------- testCafe/questions/ranking.js | 42 ++++++++++++++++++ 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/src/react/reactquestion_ranking.tsx b/src/react/reactquestion_ranking.tsx index 4d65bb4651..89fc9a3876 100644 --- a/src/react/reactquestion_ranking.tsx +++ b/src/react/reactquestion_ranking.tsx @@ -52,39 +52,41 @@ export class SurveyQuestionRanking extends SurveyQuestionElementBase { const items: Array = []; for (let i = 0; i < choices.length; i++) { const item = choices[i]; - items.push( - this.renderItem( - item, - i, - (event: PointerEvent) => { - this.question.handleKeydown.call(this.question, event, item); - }, - (event: any) => { - event.persist(); - //event.preventDefault(); - this.question.handlePointerDown.call( - this.question, - event, - item, - event.currentTarget - ); - }, - (event: any) => { - event.persist(); - //event.preventDefault(); - this.question.handlePointerUp.call( - this.question, - event, - item, - event.currentTarget - ); - }, - this.question.cssClasses, - this.question.getItemClass(item), - this.question, - unrankedItem - ) - ); + if(!!item && !item.isDisposed) { + items.push( + this.renderItem( + item, + i, + (event: PointerEvent) => { + this.question.handleKeydown.call(this.question, event, item); + }, + (event: any) => { + event.persist(); + //event.preventDefault(); + this.question.handlePointerDown.call( + this.question, + event, + item, + event.currentTarget + ); + }, + (event: any) => { + event.persist(); + //event.preventDefault(); + this.question.handlePointerUp.call( + this.question, + event, + item, + event.currentTarget + ); + }, + this.question.cssClasses, + this.question.getItemClass(item), + this.question, + unrankedItem + ) + ); + } } return items; } diff --git a/testCafe/questions/ranking.js b/testCafe/questions/ranking.js index 8cef2b47ad..a24867fd42 100644 --- a/testCafe/questions/ranking.js +++ b/testCafe/questions/ranking.js @@ -367,4 +367,46 @@ frameworks.forEach((framework) => { const value = await getValue(); await t.expect(value).eql(["BMW", "Ford"]); }); +}); +frameworks.forEach((framework) => { + fixture`${framework} ${title}`.page`${urlV2}${framework}`.beforeEach( + async (ctx) => { + const json = { + elements: [ + { + type: "checkbox", + name: "q1", + choices: ["Item1", "Item2", "Item3"], + showOtherItem: true, + }, + { + type: "ranking", + name: "q2", + choicesFromQuestion: "q1", + choicesFromQuestionMode: "selected", + }, + ], + }; + await applyTheme("defaultV2"); + await initSurvey(framework, json); + } + ); + + test("Carry forward error with others Bug#8462", async (t) => { + await t.click(Selector(".sv-string-viewer").withText("Item1")) + .click(Selector(".sv-string-viewer").withText("Item3")) + .click(Selector(".sv-string-viewer").withText("Other (describe)")) + .click(Selector("textarea")) + .pressKey("A B C"); + const item1 = Selector("span").withText("q2").parent("[data-name]").find("span").withText("Item1"); + + await t.hover(item1); + await t.drag(item1, 5, 40, { speed: 0.1 }); + const getValue = ClientFunction(()=>{ + return window["survey"].getAllQuestions()[0].value; + }); + + const value = await getValue(); + await t.expect(value).eql(["Item1", "Item3", "other"]); + }); }); \ No newline at end of file From e917de32c1f16acf7ae1a17a5edcac13e5a9f92b Mon Sep 17 00:00:00 2001 From: Andrew Telnov Date: Wed, 26 Jun 2024 14:17:40 +0300 Subject: [PATCH 2/3] Implement the fix in core #8462 --- src/dragdrop/ranking-choices.ts | 5 ++- src/question_ranking.ts | 7 +-- src/react/reactquestion_ranking.tsx | 68 ++++++++++++++--------------- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/dragdrop/ranking-choices.ts b/src/dragdrop/ranking-choices.ts index ac47bd2c02..5d9b8076f0 100644 --- a/src/dragdrop/ranking-choices.ts +++ b/src/dragdrop/ranking-choices.ts @@ -85,7 +85,10 @@ export class DragDropRankingChoices extends DragDropChoices { public getIndixies(model: any, fromChoicesArray: Array, toChoicesArray: Array) { let fromIndex = fromChoicesArray.indexOf(this.draggedElement); let toIndex = toChoicesArray.indexOf(this.dropTarget); - + if(fromIndex < 0) { + this.draggedElement = ItemValue.getItemByValue(fromChoicesArray, this.draggedElement.value) || this.draggedElement; + fromIndex = fromChoicesArray.indexOf(this.draggedElement); + } if (toIndex === -1) { const length = model.value.length; toIndex = length; diff --git a/src/question_ranking.ts b/src/question_ranking.ts index cabc16ff81..e10a3f7718 100644 --- a/src/question_ranking.ts +++ b/src/question_ranking.ts @@ -376,7 +376,7 @@ export class QuestionRankingModel extends QuestionCheckboxModel { return new DragDropRankingChoices(this.survey, null, this.longTap); } - private draggedChoise: ItemValue; + private draggedChoiceValue: any; private draggedTargetNode: HTMLElement; public handlePointerDown = ( event: PointerEvent, @@ -394,14 +394,15 @@ export class QuestionRankingModel extends QuestionCheckboxModel { this.canStartDragDueItemEnabled(choice) ) { - this.draggedChoise = choice; + this.draggedChoiceValue = choice.value; this.draggedTargetNode = node; this.dragOrClickHelper.onPointerDown(event); } }; public startDrag = (event: PointerEvent): void => { - this.dragDropRankingChoices.startDrag(event, this.draggedChoise, this, this.draggedTargetNode); + const choice = ItemValue.getItemByValue(this.activeChoices, this.draggedChoiceValue); + this.dragDropRankingChoices.startDrag(event, choice, this, this.draggedTargetNode); } public handlePointerUp = ( diff --git a/src/react/reactquestion_ranking.tsx b/src/react/reactquestion_ranking.tsx index 89fc9a3876..4d65bb4651 100644 --- a/src/react/reactquestion_ranking.tsx +++ b/src/react/reactquestion_ranking.tsx @@ -52,41 +52,39 @@ export class SurveyQuestionRanking extends SurveyQuestionElementBase { const items: Array = []; for (let i = 0; i < choices.length; i++) { const item = choices[i]; - if(!!item && !item.isDisposed) { - items.push( - this.renderItem( - item, - i, - (event: PointerEvent) => { - this.question.handleKeydown.call(this.question, event, item); - }, - (event: any) => { - event.persist(); - //event.preventDefault(); - this.question.handlePointerDown.call( - this.question, - event, - item, - event.currentTarget - ); - }, - (event: any) => { - event.persist(); - //event.preventDefault(); - this.question.handlePointerUp.call( - this.question, - event, - item, - event.currentTarget - ); - }, - this.question.cssClasses, - this.question.getItemClass(item), - this.question, - unrankedItem - ) - ); - } + items.push( + this.renderItem( + item, + i, + (event: PointerEvent) => { + this.question.handleKeydown.call(this.question, event, item); + }, + (event: any) => { + event.persist(); + //event.preventDefault(); + this.question.handlePointerDown.call( + this.question, + event, + item, + event.currentTarget + ); + }, + (event: any) => { + event.persist(); + //event.preventDefault(); + this.question.handlePointerUp.call( + this.question, + event, + item, + event.currentTarget + ); + }, + this.question.cssClasses, + this.question.getItemClass(item), + this.question, + unrankedItem + ) + ); } return items; } From fabc12efbf12a35b876ae19d8580ab38253d6822 Mon Sep 17 00:00:00 2001 From: Andrew Telnov Date: Wed, 26 Jun 2024 14:25:28 +0300 Subject: [PATCH 3/3] Fix unit test and typos #8462 --- src/dragdrop/ranking-choices.ts | 6 +++--- src/dragdrop/ranking-select-to-rank.ts | 2 +- tests/dragdrophelpertests.ts | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/dragdrop/ranking-choices.ts b/src/dragdrop/ranking-choices.ts index 5d9b8076f0..5cb12576a7 100644 --- a/src/dragdrop/ranking-choices.ts +++ b/src/dragdrop/ranking-choices.ts @@ -82,10 +82,10 @@ export class DragDropRankingChoices extends DragDropChoices { const node = this.domAdapter.draggedElementShortcut.querySelector(".sv-ranking-item"); node.style.cursor = "grabbing"; }; - public getIndixies(model: any, fromChoicesArray: Array, toChoicesArray: Array) { + public getIndices(model: any, fromChoicesArray: Array, toChoicesArray: Array) { let fromIndex = fromChoicesArray.indexOf(this.draggedElement); let toIndex = toChoicesArray.indexOf(this.dropTarget); - if(fromIndex < 0) { + if(fromIndex < 0 && !!this.draggedElement) { this.draggedElement = ItemValue.getItemByValue(fromChoicesArray, this.draggedElement.value) || this.draggedElement; fromIndex = fromChoicesArray.indexOf(this.draggedElement); } @@ -103,7 +103,7 @@ export class DragDropRankingChoices extends DragDropChoices { } protected afterDragOver(dropTargetNode: HTMLElement): void { - const { fromIndex, toIndex } = this.getIndixies(this.parentElement, this.parentElement.rankingChoices, this.parentElement.rankingChoices); + const { fromIndex, toIndex } = this.getIndices(this.parentElement, this.parentElement.rankingChoices, this.parentElement.rankingChoices); this.reorderRankedItem(this.parentElement as QuestionRankingModel, fromIndex, toIndex); } diff --git a/src/dragdrop/ranking-select-to-rank.ts b/src/dragdrop/ranking-select-to-rank.ts index 61f9cd4921..0f85fa1f80 100644 --- a/src/dragdrop/ranking-select-to-rank.ts +++ b/src/dragdrop/ranking-select-to-rank.ts @@ -78,7 +78,7 @@ export class DragDropRankingSelectToRank extends DragDropRankingChoices { ): void { const questionModel: any = this.parentElement; - let { fromIndex, toIndex } = this.getIndixies(questionModel, fromChoicesArray, toChoicesArray); + let { fromIndex, toIndex } = this.getIndices(questionModel, fromChoicesArray, toChoicesArray); rankFunction(questionModel, fromIndex, toIndex, dropTargetNode); } diff --git a/tests/dragdrophelpertests.ts b/tests/dragdrophelpertests.ts index be75db6c90..7dd074b581 100644 --- a/tests/dragdrophelpertests.ts +++ b/tests/dragdrophelpertests.ts @@ -318,56 +318,56 @@ QUnit.test("DragDropRankingSelectToRank reorderRankedItem", function (assert) { assert.equal(questionModel.rankingChoices.length, 2, "rankingChoices count"); }); -QUnit.test("DragDropRankingSelectToRank getIndixies", function (assert) { +QUnit.test("DragDropRankingSelectToRank getIndices", function (assert) { const withDefaultValue = true; const dndModel = new DragDropRankingSelectToRank(); const questionModel = createRankingQuestionModel(withDefaultValue); - let toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.unRankingChoices).toIndex; + let toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.unRankingChoices).toIndex; assert.equal(toIndex, 2); - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; assert.equal(toIndex, 2); dndModel.draggedElement = questionModel.rankingChoices[0]; dndModel.dropTarget = questionModel.rankingChoices[1]; dndModel["_isBottom"] = false; - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; assert.equal(toIndex, 0); dndModel.draggedElement = questionModel.rankingChoices[0]; dndModel.dropTarget = questionModel.rankingChoices[1]; dndModel["_isBottom"] = true; - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; assert.equal(toIndex, 1); dndModel.draggedElement = questionModel.rankingChoices[1]; dndModel.dropTarget = questionModel.rankingChoices[0]; dndModel["_isBottom"] = false; - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; assert.equal(toIndex, 0); dndModel.draggedElement = questionModel.rankingChoices[1]; dndModel.dropTarget = questionModel.rankingChoices[0]; dndModel["_isBottom"] = true; - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; assert.equal(toIndex, 1); dndModel.dropTarget = questionModel.unRankingChoices[0]; dndModel.draggedElement = questionModel.rankingChoices[1]; dndModel["_isBottom"] = true; - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.unRankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.unRankingChoices).toIndex; assert.equal(toIndex, 1); dndModel["_isBottom"] = false; dndModel.dropTarget = questionModel.unRankingChoices[0]; dndModel.draggedElement = questionModel.rankingChoices[1]; - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.unRankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.unRankingChoices).toIndex; assert.equal(toIndex, 0); questionModel.value = ["11", "22", "33"]; dndModel.draggedElement = questionModel.rankingChoices[0]; dndModel.dropTarget = questionModel.rankingChoices[1]; dndModel["_isBottom"] = true; - toIndex = dndModel.getIndixies(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; + toIndex = dndModel.getIndices(questionModel, questionModel.rankingChoices, questionModel.rankingChoices).toIndex; assert.equal(toIndex, 1); }); // EO selectToRankEnabled