fix: error message in rating Question (#6909)

Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
Co-authored-by: Matti Nannt <matti@formbricks.com>
This commit is contained in:
Johannes
2025-12-03 01:15:34 -08:00
committed by GitHub
parent 4c94fc25ae
commit cfdf09650f
5 changed files with 147 additions and 115 deletions

View File

@@ -294,28 +294,28 @@ export const BlockCard = ({
open={!isBlockCollapsed}
onOpenChange={() => setIsBlockCollapsed(!isBlockCollapsed)}
className={cn(isBlockCollapsed ? "h-full" : "")}>
<Collapsible.CollapsibleTrigger
asChild
className="block h-full w-full cursor-pointer hover:bg-slate-100">
<div className="flex h-full items-center justify-between px-4 py-2">
<div className="flex items-center gap-2">
<div>
<h4 className="text-sm font-medium text-slate-700">{block.name}</h4>
<p className="text-xs text-slate-500">
{blockElementsCount} {blockElementsCountText}
</p>
<Collapsible.CollapsibleTrigger asChild>
<div className="block h-full w-full cursor-pointer hover:bg-slate-100">
<div className="flex h-full items-center justify-between px-4 py-2">
<div className="flex items-center gap-2">
<div>
<h4 className="text-sm font-medium text-slate-700">{block.name}</h4>
<p className="text-xs text-slate-500">
{blockElementsCount} {blockElementsCountText}
</p>
</div>
</div>
<div>
<BlockMenu
isFirstBlock={blockIdx === 0}
isLastBlock={blockIdx === totalBlocks - 1}
isOnlyBlock={totalBlocks === 1}
onDuplicate={() => duplicateBlock(block.id)}
onDelete={() => deleteBlock(block.id)}
onMoveUp={() => moveBlock(block.id, "up")}
onMoveDown={() => moveBlock(block.id, "down")}
/>
</div>
</div>
<div>
<BlockMenu
isFirstBlock={blockIdx === 0}
isLastBlock={blockIdx === totalBlocks - 1}
isOnlyBlock={totalBlocks === 1}
onDuplicate={() => duplicateBlock(block.id)}
onDelete={() => deleteBlock(block.id)}
onMoveUp={() => moveBlock(block.id, "up")}
onMoveDown={() => moveBlock(block.id, "down")}
/>
</div>
</div>
</Collapsible.CollapsibleTrigger>

View File

@@ -62,7 +62,7 @@ test.describe("Survey Create & Submit Response without logic", async () => {
await expect(page.getByPlaceholder(surveys.createAndSubmit.openTextQuestion.placeholder)).toBeVisible();
await page
.getByPlaceholder(surveys.createAndSubmit.openTextQuestion.placeholder)
.fill("This is my Open Text answer");
.fill("Open Text answer");
await page.locator("#questionCard-0").getByRole("button", { name: "Next" }).click();
// Single Select Question
@@ -116,7 +116,7 @@ test.describe("Survey Create & Submit Response without logic", async () => {
expect(await page.getByRole("group", { name: "Choices" }).locator("label").count()).toBe(5);
await expect(page.locator("#questionCard-3").getByRole("button", { name: "Next" })).toBeVisible();
await expect(page.locator("#questionCard-3").getByRole("button", { name: "Back" })).toBeVisible();
await page.locator("path").nth(3).click();
await page.getByRole("radio", { name: "Rate 3 out of" }).check();
await page.locator("#questionCard-3").getByRole("button", { name: "Next" }).click();
// NPS Question
@@ -212,11 +212,9 @@ test.describe("Survey Create & Submit Response without logic", async () => {
// Address Question
await expect(page.getByText(surveys.createAndSubmit.address.question)).toBeVisible();
await expect(page.getByLabel(surveys.createAndSubmit.address.placeholder.addressLine1)).toBeVisible();
await page
.getByLabel(surveys.createAndSubmit.address.placeholder.addressLine1)
.fill("This is my Address");
await page.getByLabel(surveys.createAndSubmit.address.placeholder.addressLine1).fill("Address");
await expect(page.getByLabel(surveys.createAndSubmit.address.placeholder.city)).toBeVisible();
await page.getByLabel(surveys.createAndSubmit.address.placeholder.city).fill("This is my city");
await page.getByLabel(surveys.createAndSubmit.address.placeholder.city).fill("city");
await expect(page.getByLabel(surveys.createAndSubmit.address.placeholder.zip)).toBeVisible();
await page.getByLabel(surveys.createAndSubmit.address.placeholder.zip).fill("12345");
await page.locator("#questionCard-10").getByRole("button", { name: "Next" }).click();
@@ -785,7 +783,7 @@ test.describe("Testing Survey with advanced logic", async () => {
).toBeVisible();
await page
.getByPlaceholder(surveys.createWithLogicAndSubmit.openTextQuestion.placeholder)
.fill("This is my Open Text answer");
.fill("Open Text answer");
await page.locator("#questionCard-0").getByRole("button", { name: "Next" }).click();
// Single Select Question
@@ -858,10 +856,9 @@ test.describe("Testing Survey with advanced logic", async () => {
await expect(
page.locator("#questionCard-4").getByText(surveys.createWithLogicAndSubmit.ratingQuestion.highLabel)
).toBeVisible();
expect(await page.getByRole("group", { name: "Choices" }).locator("label").count()).toBe(5);
await expect(page.locator("#questionCard-4").getByRole("button", { name: "Next" })).toBeVisible();
await expect(page.locator("#questionCard-4").getByRole("button", { name: "Back" })).toBeVisible();
await page.getByRole("group", { name: "Choices" }).locator("path").nth(3).click();
await page.getByRole("radio", { name: "Rate 4 out of" }).check();
await page.locator("#questionCard-4").getByRole("button", { name: "Next" }).click();
// NPS Question
@@ -972,11 +969,9 @@ test.describe("Testing Survey with advanced logic", async () => {
).toBeVisible();
await page
.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.addressLine1)
.fill("This is my Address");
.fill("Address");
await expect(page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.city)).toBeVisible();
await page
.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.city)
.fill("This is my city");
await page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.city).fill("city");
await expect(page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.zip)).toBeVisible();
await page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.zip).fill("12345");
await page.locator("#questionCard-13").getByRole("button", { name: "Finish" }).click();
@@ -997,13 +992,26 @@ test.describe("Testing Survey with advanced logic", async () => {
const updatedUrl = currentUrl.replace("summary?share=true", "responses");
await page.goto(updatedUrl);
await page.waitForSelector("#response-table");
await page.waitForSelector("table#response-table");
await expect(page.getByRole("cell", { name: "score" })).toBeVisible();
await page.waitForLoadState("networkidle");
await page.waitForTimeout(5000);
await expect(page.getByRole("cell", { name: "32", exact: true })).toBeVisible();
await page.pause();
// Look for any cell containing "32" or a score-related value
const scoreCell = page.getByRole("cell").filter({ hasText: /^32/ });
await expect(scoreCell).toBeVisible({
timeout: 15000,
});
// Look for the secret message in the table
const secretCell = page.getByRole("cell").filter({ hasText: /This is a secret message for e2e tests/ });
await expect(secretCell).toBeVisible({
timeout: 15000,
});
});
});
});

View File

@@ -656,7 +656,7 @@ export const createSurveyWithLogic = async (page: Page, params: CreateSurveyWith
await page.locator("#action-2-operator").click();
await page.getByRole("option", { name: "Assign =" }).click();
await page.locator("#action-2-value-input").click();
await page.locator("#action-2-value-input").fill("1");
await page.locator("#action-2-value-input").fill("This ");
// Close Block 1 settings before moving to Block 2
await page
.locator("div")

View File

@@ -106,51 +106,51 @@ export const surveys = {
createAndSubmit: {
welcomeCard: {
headline: "Welcome to My Testing Survey Welcome Card!",
description: "This is the description of my Welcome Card!",
description: "the description of my Welcome Card!",
},
openTextQuestion: {
question: "This is my Open Text Question",
description: "This is my Open Text Description",
placeholder: "This is my Placeholder",
question: "Open Text Question",
description: "Open Text Description",
placeholder: "Placeholder",
},
singleSelectQuestion: {
question: "This is my Single Select Question",
description: "This is my Single Select Description",
question: "Single Select Question",
description: "Single Select Description",
options: ["Option 1", "Option 2"],
},
multiSelectQuestion: {
question: "This is my Multi Select Question",
description: "This is Multi Select Description",
question: "Multi Select Question",
description: "Multi Select Description",
options: ["Option 1", "Option 2", "Option 3"],
},
ratingQuestion: {
question: "This is my Rating Question",
description: "This is Rating Description",
question: "Rating Question",
description: "Rating Description",
lowLabel: "My Lower Label",
highLabel: "My Upper Label",
},
npsQuestion: {
question: "This is my NPS Question",
question: "NPS Question",
lowLabel: "My Lower Label",
highLabel: "My Upper Label",
},
ctaQuestion: {
question: "This is my CTA Question",
question: "CTA Question",
buttonLabel: "My Button Label",
},
consentQuestion: {
question: "This is my Consent Question",
question: "Consent Question",
checkboxLabel: "My Checkbox Label",
},
pictureSelectQuestion: {
question: "This is my Picture Select Question",
description: "This is Picture Select Description",
question: "Picture Select Question",
description: "Picture Select Description",
},
dateQuestion: {
question: "This is my Date Question",
question: "Date Question",
},
fileUploadQuestion: {
question: "This is my File Upload Question",
question: "File Upload Question",
},
matrix: {
question: "How much do you love these flowers?",
@@ -178,57 +178,57 @@ export const surveys = {
createWithLogicAndSubmit: {
welcomeCard: {
headline: "Welcome to My Testing Survey Welcome Card!",
description: "This is the description of my Welcome Card!",
description: "the description of my Welcome Card!",
},
openTextQuestion: {
question: "This is my Open Text Question",
description: "This is my Open Text Description",
placeholder: "This is my Placeholder",
question: "Open Text Question",
description: "Open Text Description",
placeholder: "Placeholder",
},
singleSelectQuestion: {
question: "This is my Single Select Question",
description: "This is my Single Select Description",
question: "Single Select Question",
description: "Single Select Description",
options: ["Option 1", "Option 2"],
},
multiSelectQuestion: {
question: "This is my Multi Select Question",
description: "This is Multi Select Description",
question: "Multi Select Question",
description: "Multi Select Description",
options: ["Option 1", "Option 2", "Option 3"],
},
ratingQuestion: {
question: "This is my Rating Question",
description: "This is Rating Description",
question: "Rating Question",
description: "Rating Description",
lowLabel: "My Lower Label",
highLabel: "My Upper Label",
},
npsQuestion: {
question: "This is my NPS Question",
question: "NPS Question",
lowLabel: "My Lower Label",
highLabel: "My Upper Label",
},
ctaQuestion: {
question: "This is my CTA Question",
question: "CTA Question",
buttonLabel: "My Button Label",
},
consentQuestion: {
question: "This is my Consent Question",
question: "Consent Question",
checkboxLabel: "My Checkbox Label",
},
pictureSelectQuestion: {
question: "This is my Picture Select Question",
description: "This is Picture Select Description",
question: "Picture Select Question",
description: "Picture Select Description",
},
fileUploadQuestion: {
question: "This is my File Upload Question",
question: "File Upload Question",
},
date: {
question: "This is my Date Question",
question: "Date Question",
},
cal: {
question: "This is my cal Question",
question: "cal Question",
},
matrix: {
question: "This is my Matrix Question",
question: "Matrix Question",
description: "0: Not at all, 3: Love it",
rows: ["Roses", "Trees", "Ocean"],
columns: ["0", "1", "2", "3"],
@@ -242,7 +242,7 @@ export const surveys = {
},
},
ranking: {
question: "This is my Ranking Question",
question: "Ranking Question",
choices: ["Work", "Money", "Travel", "Family", "Friends"],
},
endingCard: {
@@ -342,12 +342,12 @@ export const actions = {
noCode: {
click: {
name: "Create Click Action (CSS Selector)",
description: "This is my Create Action (click, CSS Selector)",
description: "Create Action (click, CSS Selector)",
selector: ".my-custom-class",
},
pageView: {
name: "Create Page view Action (specific Page URL)",
description: "This is my Create Action (Page view)",
description: "Create Action (Page view)",
matcher: {
label: "Contains",
value: "custom-url",
@@ -355,16 +355,16 @@ export const actions = {
},
exitIntent: {
name: "Create Exit Intent Action",
description: "This is my Create Action (Exit Intent)",
description: "Create Action (Exit Intent)",
},
fiftyPercentScroll: {
name: "Create 50% Scroll Action",
description: "This is my Create Action (50% Scroll)",
description: "Create Action (50% Scroll)",
},
},
code: {
name: "Create Action (Code)",
description: "This is my Create Action (Code)",
description: "Create Action (Code)",
key: "Create Action (Code)",
},
},
@@ -372,12 +372,12 @@ export const actions = {
noCode: {
click: {
name: "Edit Click Action (CSS Selector)",
description: "This is my Edit Action (click, CSS Selector)",
description: "Edit Action (click, CSS Selector)",
selector: ".my-custom-class-edited",
},
pageView: {
name: "Edit Page view Action (specific Page URL)",
description: "This is my Edit Action (Page view)",
description: "Edit Action (Page view)",
matcher: {
label: "Starts with",
value: "custom-url0-edited",
@@ -386,26 +386,26 @@ export const actions = {
},
exitIntent: {
name: "Edit Exit Intent Action",
description: "This is my Edit Action (Exit Intent)",
description: "Edit Action (Exit Intent)",
},
fiftyPercentScroll: {
name: "Edit 50% Scroll Action",
description: "This is my Edit Action (50% Scroll)",
description: "Edit Action (50% Scroll)",
},
},
code: {
description: "This is my Edit Action (Code)",
description: "Edit Action (Code)",
},
},
delete: {
noCode: {
name: "Delete click Action (CSS Selector)",
description: "This is my Delete Action (CSS Selector)",
description: "Delete Action (CSS Selector)",
selector: ".my-custom-class-deleted",
},
code: {
name: "Delete Action (Code)",
description: "This is my Delete Action (Code)",
description: "Delete Action (Code)",
},
},
};

View File

@@ -54,23 +54,6 @@ export function RatingElement({
setTtc(updatedTtcObj);
};
function HiddenRadioInput({ number, id }: { number: number; id?: string }) {
return (
<input
type="radio"
id={id}
name="rating"
value={number}
className="fb-invisible fb-absolute fb-left-0 fb-h-full fb-w-full fb-cursor-pointer fb-opacity-0"
onClick={() => {
handleSelect(number);
}}
required={element.required}
checked={value === number}
/>
);
}
useEffect(() => {
setHoveredNumber(0);
}, [element.id, setHoveredNumber]);
@@ -96,14 +79,6 @@ export function RatingElement({
setTtc(updatedTtcObj);
};
const handleKeyDown = (number: number) => (e: KeyboardEvent) => {
const isActivationKey = e.key === " " || e.key === "Enter";
if (isActivationKey) {
e.preventDefault();
handleSelect(number);
}
};
const handleMouseOver = (number: number) => () => {
setHoveredNumber(number);
};
@@ -159,6 +134,17 @@ export function RatingElement({
);
};
const getRatingInputId = (number: number) => `${element.id}-${number}`;
const handleKeyDown = (number: number) => (e: KeyboardEvent) => {
if (e.key === " ") {
e.preventDefault();
const inputId = getRatingInputId(number);
document.getElementById(inputId)?.click();
document.getElementById(inputId)?.focus();
}
};
const renderNumberScale = (number: number, totalLength: number) => {
return (
<label
@@ -170,7 +156,19 @@ export function RatingElement({
className={`fb-absolute fb-left-0 fb-top-0 fb-h-[6px] fb-w-full ${getRatingNumberOptionColor(element.range, number)}`}
/>
)}
<HiddenRadioInput number={number} id={number.toString()} />
<input
type="radio"
id={getRatingInputId(number)}
name="rating"
value={number}
className="fb-absolute fb-left-0 fb-h-full fb-w-full fb-cursor-pointer fb-opacity-0"
onClick={() => {
handleSelect(number);
}}
required={element.required}
checked={value === number}
tabIndex={-1}
/>
{number}
</label>
);
@@ -179,12 +177,25 @@ export function RatingElement({
const renderStarScale = (number: number) => {
return (
<label
aria-label={`Rate ${number} out of ${element.range}`}
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
onKeyDown={handleKeyDown(number)}
className={getStarLabelClassName(number)}
onFocus={handleFocus(number)}
onBlur={handleBlur}>
<HiddenRadioInput number={number} id={number.toString()} />
<input
type="radio"
id={getRatingInputId(number)}
name="rating"
value={number}
className="fb-absolute fb-left-0 fb-h-full fb-w-full fb-cursor-pointer fb-opacity-0"
onClick={() => {
handleSelect(number);
}}
required={element.required}
checked={value === number}
tabIndex={-1}
/>
<div className="fb-h-full fb-w-full fb-max-w-[74px] fb-object-contain">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path
@@ -200,12 +211,25 @@ export function RatingElement({
const renderSmileyScale = (number: number, idx: number) => {
return (
<label
aria-label={`Rate ${number} out of ${element.range}`}
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
className={getSmileyLabelClassName(number)}
onKeyDown={handleKeyDown(number)}
onFocus={handleFocus(number)}
onBlur={handleBlur}>
<HiddenRadioInput number={number} id={number.toString()} />
<input
type="radio"
id={getRatingInputId(number)}
name="rating"
value={number}
className="fb-absolute fb-left-0 fb-h-full fb-w-full fb-cursor-pointer fb-opacity-0"
onClick={() => {
handleSelect(number);
}}
required={element.required}
checked={value === number}
tabIndex={-1}
/>
<div className="fb-h-full fb-w-full fb-max-w-[74px] fb-object-contain">
<RatingSmiley
active={value === number || hoveredNumber === number}
@@ -253,7 +277,7 @@ export function RatingElement({
renderRatingOption(number, i, a.length)
)}
</div>
<div className="fb-text-subheading fb-mt-4 fb-flex fb-justify-between fb-px-1.5 fb-text-xs fb-leading-6 fb-gap-8">
<div className="fb-text-subheading fb-mt-8 fb-flex fb-justify-between fb-px-1.5 fb-text-xs fb-leading-6 fb-gap-8">
<p className="fb-max-w-[50%]" dir="auto">
{getLocalizedValue(element.lowerLabel, languageCode)}
</p>