mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
chore: track server action with sentry and general fixes (#5799)
This commit is contained in:
@@ -11,9 +11,7 @@
|
||||
"clean": "rimraf .turbo node_modules dist storybook-static"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-plugin-react-refresh": "0.4.20",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
"eslint-plugin-react-refresh": "0.4.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "3.2.6",
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("SentryProvider", () => {
|
||||
expect(initSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
dsn: sentryDsn,
|
||||
tracesSampleRate: 1,
|
||||
tracesSampleRate: 0,
|
||||
debug: false,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
replaysSessionSampleRate: 0.1,
|
||||
@@ -81,6 +81,26 @@ describe("SentryProvider", () => {
|
||||
expect(screen.getByTestId("child")).toHaveTextContent("Test Content");
|
||||
});
|
||||
|
||||
test("does not reinitialize Sentry when props change after initial render", () => {
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
const { rerender } = render(
|
||||
<SentryProvider sentryDsn={sentryDsn} isEnabled>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
expect(initSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(
|
||||
<SentryProvider sentryDsn="https://newDsn@o0.ingest.sentry.io/0" isEnabled={false}>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
expect(initSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("processes beforeSend correctly", () => {
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
@@ -109,4 +129,36 @@ describe("SentryProvider", () => {
|
||||
const hintWithoutError = { originalException: undefined };
|
||||
expect(beforeSend(dummyEvent, hintWithoutError)).toEqual(dummyEvent);
|
||||
});
|
||||
|
||||
test("processes beforeSend correctly when hint.originalException is not an Error object", () => {
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
render(
|
||||
<SentryProvider sentryDsn={sentryDsn} isEnabled>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
const config = initSpy.mock.calls[0][0];
|
||||
expect(config).toHaveProperty("beforeSend");
|
||||
const beforeSend = config.beforeSend;
|
||||
|
||||
if (!beforeSend) {
|
||||
throw new Error("beforeSend is not defined");
|
||||
}
|
||||
|
||||
const dummyEvent = { some: "event" } as unknown as Sentry.ErrorEvent;
|
||||
|
||||
const hintWithString = { originalException: "string exception" };
|
||||
expect(() => beforeSend(dummyEvent, hintWithString)).not.toThrow();
|
||||
expect(beforeSend(dummyEvent, hintWithString)).toEqual(dummyEvent);
|
||||
|
||||
const hintWithNumber = { originalException: 123 };
|
||||
expect(() => beforeSend(dummyEvent, hintWithNumber)).not.toThrow();
|
||||
expect(beforeSend(dummyEvent, hintWithNumber)).toEqual(dummyEvent);
|
||||
|
||||
const hintWithNull = { originalException: null };
|
||||
expect(() => beforeSend(dummyEvent, hintWithNull)).not.toThrow();
|
||||
expect(beforeSend(dummyEvent, hintWithNull)).toEqual(dummyEvent);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,8 +15,8 @@ export const SentryProvider = ({ children, sentryDsn, isEnabled }: SentryProvide
|
||||
Sentry.init({
|
||||
dsn: sentryDsn,
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
// No tracing while Sentry doesn't update to telemetry 2.0.0 - https://github.com/getsentry/sentry-javascript/issues/15737
|
||||
tracesSampleRate: 0,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
@@ -15,12 +15,20 @@ import {
|
||||
describe("Time Utilities", () => {
|
||||
describe("convertDateString", () => {
|
||||
test("should format date string correctly", () => {
|
||||
expect(convertDateString("2024-03-20")).toBe("Mar 20, 2024");
|
||||
expect(convertDateString("2024-03-20:12:30:00")).toBe("Mar 20, 2024");
|
||||
});
|
||||
|
||||
test("should return empty string for empty input", () => {
|
||||
expect(convertDateString("")).toBe("");
|
||||
});
|
||||
|
||||
test("should return null for null input", () => {
|
||||
expect(convertDateString(null as any)).toBe(null);
|
||||
});
|
||||
|
||||
test("should handle invalid date strings", () => {
|
||||
expect(convertDateString("not-a-date")).toBe("Invalid Date");
|
||||
});
|
||||
});
|
||||
|
||||
describe("convertDateTimeString", () => {
|
||||
@@ -73,7 +81,7 @@ describe("Time Utilities", () => {
|
||||
|
||||
describe("formatDate", () => {
|
||||
test("should format date correctly", () => {
|
||||
const date = new Date("2024-03-20");
|
||||
const date = new Date(2024, 2, 20); // March is month 2 (0-based)
|
||||
expect(formatDate(date)).toBe("March 20, 2024");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,11 +2,16 @@ import { formatDistance, intlFormat } from "date-fns";
|
||||
import { de, enUS, fr, pt, ptBR, zhTW } from "date-fns/locale";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
|
||||
export const convertDateString = (dateString: string) => {
|
||||
export const convertDateString = (dateString: string | null) => {
|
||||
if (dateString === null) return null;
|
||||
if (!dateString) {
|
||||
return dateString;
|
||||
}
|
||||
|
||||
const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) {
|
||||
return "Invalid Date";
|
||||
}
|
||||
return intlFormat(
|
||||
date,
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getUser } from "@/lib/user/service";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from "next-safe-action";
|
||||
import { logger } from "@formbricks/logger";
|
||||
@@ -14,6 +15,8 @@ import {
|
||||
|
||||
export const actionClient = createSafeActionClient({
|
||||
handleServerError(e) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
if (
|
||||
e instanceof ResourceNotFoundError ||
|
||||
e instanceof AuthorizationError ||
|
||||
|
||||
@@ -53,10 +53,10 @@ describe("SurveyCard", () => {
|
||||
survey={{ ...dummySurvey, status: "draft" } as unknown as TSurvey}
|
||||
environmentId={environmentId}
|
||||
isReadOnly={false}
|
||||
WEBAPP_URL={WEBAPP_URL}
|
||||
duplicateSurvey={mockDuplicateSurvey}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
locale="en-US"
|
||||
surveyDomain={WEBAPP_URL}
|
||||
/>
|
||||
);
|
||||
// Draft survey => link should point to edit
|
||||
@@ -70,10 +70,10 @@ describe("SurveyCard", () => {
|
||||
survey={{ ...dummySurvey, status: "draft" } as unknown as TSurvey}
|
||||
environmentId={environmentId}
|
||||
isReadOnly={true}
|
||||
WEBAPP_URL={WEBAPP_URL}
|
||||
duplicateSurvey={mockDuplicateSurvey}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
locale="en-US"
|
||||
surveyDomain={WEBAPP_URL}
|
||||
/>
|
||||
);
|
||||
// When it's read only and draft, we expect no link
|
||||
@@ -87,10 +87,10 @@ describe("SurveyCard", () => {
|
||||
survey={{ ...dummySurvey, status: "inProgress" } as unknown as TSurvey}
|
||||
environmentId={environmentId}
|
||||
isReadOnly={false}
|
||||
WEBAPP_URL={WEBAPP_URL}
|
||||
duplicateSurvey={mockDuplicateSurvey}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
locale="en-US"
|
||||
surveyDomain={WEBAPP_URL}
|
||||
/>
|
||||
);
|
||||
// For non-draft => link to summary
|
||||
|
||||
@@ -127,9 +127,9 @@ input[type="search"]::-ms-reveal {
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
background: #0f172a;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
-webkit-appearance: none;
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
@@ -117,11 +117,9 @@
|
||||
"prismjs": "1.30.0",
|
||||
"qr-code-styling": "1.9.2",
|
||||
"qrcode": "1.5.4",
|
||||
"react": "19.1.0",
|
||||
"react-colorful": "5.6.1",
|
||||
"react-confetti": "6.4.0",
|
||||
"react-day-picker": "9.6.7",
|
||||
"react-dom": "19.1.0",
|
||||
"react-hook-form": "7.56.2",
|
||||
"react-hot-toast": "2.5.2",
|
||||
"react-turnstile": "1.1.4",
|
||||
|
||||
@@ -12,8 +12,8 @@ if (SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
// No tracing while Sentry doesn't update to telemetry 2.0.0 - https://github.com/getsentry/sentry-javascript/issues/15737
|
||||
tracesSampleRate: 0,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
@@ -11,8 +11,8 @@ if (SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
// No tracing while Sentry doesn't update to telemetry 2.0.0 - https://github.com/getsentry/sentry-javascript/issues/15737
|
||||
tracesSampleRate: 0,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"schema": "packages/database/schema.prisma"
|
||||
},
|
||||
"scripts": {
|
||||
"clean:all": "turbo run clean && rimraf node_modules pnpm-lock.yaml .turbo coverage out",
|
||||
"clean": "turbo run clean && rimraf node_modules .turbo coverage out",
|
||||
"build": "turbo run build",
|
||||
"build:dev": "turbo run build:dev",
|
||||
@@ -35,6 +36,10 @@
|
||||
"fb-migrate-dev": "pnpm --filter @formbricks/database create-migration && pnpm prisma generate",
|
||||
"tolgee-pull": "BRANCH_NAME=$(node -p \"require('./branch.json').branchName\") && tolgee pull --tags \"draft:$BRANCH_NAME\" \"production\" && prettier --write ./apps/web/locales/*.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@azure/microsoft-playwright-testing": "1.0.0-beta.7",
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { type TResponseData, type TResponseVariables } from "@formbricks/types/responses";
|
||||
import { type TSurveyQuestion, TSurveyQuestionTypeEnum } from "../../../types/surveys/types";
|
||||
import { parseRecallInformation, replaceRecallInfo } from "./recall";
|
||||
@@ -15,7 +15,7 @@ vi.mock("./i18n", () => ({
|
||||
vi.mock("./date-time", () => ({
|
||||
isValidDateString: (val: string) => /^\d{4}-\d{2}-\d{2}$/.test(val) || /^\d{2}-\d{2}-\d{4}$/.test(val),
|
||||
formatDateWithOrdinal: (date: Date) =>
|
||||
`${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}-${("0" + date.getDate()).slice(-2)}_formatted`,
|
||||
`${date.getUTCFullYear()}-${("0" + (date.getUTCMonth() + 1)).slice(-2)}-${("0" + date.getUTCDate()).slice(-2)}_formatted`,
|
||||
}));
|
||||
|
||||
describe("replaceRecallInfo", () => {
|
||||
@@ -34,79 +34,73 @@ describe("replaceRecallInfo", () => {
|
||||
lastLogin: "2024-03-10",
|
||||
};
|
||||
|
||||
it("should replace recall info from responseData", () => {
|
||||
test("should replace recall info from responseData", () => {
|
||||
const text = "Welcome, #recall:name/fallback:Guest#! Your email is #recall:email/fallback:N/A#.";
|
||||
const expected = "Welcome, John Doe! Your email is john.doe@example.com.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should replace recall info from variables if not in responseData", () => {
|
||||
test("should replace recall info from variables if not in responseData", () => {
|
||||
const text = "Product: #recall:productName/fallback:N/A#. Role: #recall:userRole/fallback:User#.";
|
||||
const expected = "Product: Formbricks. Role: Admin.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should use fallback if value is not found in responseData or variables", () => {
|
||||
test("should use fallback if value is not found in responseData or variables", () => {
|
||||
const text = "Your organization is #recall:orgName/fallback:DefaultOrg#.";
|
||||
const expected = "Your organization is DefaultOrg.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should handle nbsp in fallback", () => {
|
||||
const text = "Status: #recall:status/fallback:PendingnbspReview#.";
|
||||
const expected = "Status: Pending Review.";
|
||||
test("should handle nbsp in fallback", () => {
|
||||
const text = "Status: #recall:status/fallback:Pending Review#.";
|
||||
const expected = "Status: Pending& ;Review.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should format date strings from responseData", () => {
|
||||
test("should format date strings from responseData", () => {
|
||||
const text = "Registered on: #recall:registrationDate/fallback:N/A#.";
|
||||
const expected = "Registered on: 2023-01-15_formatted.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should format date strings from variables", () => {
|
||||
test("should format date strings from variables", () => {
|
||||
const text = "Last login: #recall:lastLogin/fallback:N/A#.";
|
||||
const expected = "Last login: 2024-03-10_formatted.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should join array values with a comma and space", () => {
|
||||
test("should join array values with a comma and space", () => {
|
||||
const text = "Tags: #recall:tags/fallback:none#.";
|
||||
const expected = "Tags: beta, user.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should handle empty array values, replacing with fallback", () => {
|
||||
test("should handle empty array values, replacing with fallback", () => {
|
||||
const text = "Categories: #recall:emptyArray/fallback:No Categories#.";
|
||||
const expected = "Categories: No& ;Categories.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should handle null values from responseData, replacing with fallback", () => {
|
||||
const text = "Preference: #recall:nullValue/fallback:Not Set#.";
|
||||
const expected = "Preference: Not& ;Set.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should handle multiple recall patterns in a single string", () => {
|
||||
test("should handle multiple recall patterns in a single string", () => {
|
||||
const text =
|
||||
"Hi #recall:name/fallback:User#, welcome to #recall:productName/fallback:Our Product#. Your role is #recall:userRole/fallback:Member#.";
|
||||
const expected = "Hi John Doe, welcome to #recall:productName/fallback:Our Product#. Your role is Admin.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should return original text if no recall pattern is found", () => {
|
||||
test("should return original text if no recall pattern is found", () => {
|
||||
const text = "This is a normal text without recall info.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(text);
|
||||
});
|
||||
|
||||
it("should handle recall ID not found, using fallback", () => {
|
||||
test("should handle recall ID not found, using fallback", () => {
|
||||
const text = "Value: #recall:nonExistent/fallback:FallbackValue#.";
|
||||
const expected = "Value: FallbackValue.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should handle if recall info is incomplete (e.g. missing fallback part), effectively using empty fallback", () => {
|
||||
test("should handle if recall info is incomplete (e.g. missing fallback part), effectively using empty fallback", () => {
|
||||
// This specific pattern is not fully matched by extractRecallInfo, leading to no replacement.
|
||||
// The current extractRecallInfo expects #recall:ID/fallback:VALUE#
|
||||
const text = "Test: #recall:name#";
|
||||
@@ -114,10 +108,28 @@ describe("replaceRecallInfo", () => {
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should handle complex fallback with spaces and special characters encoded as nbsp", () => {
|
||||
test("should handle complex fallback with spaces and special characters encoded as nbsp", () => {
|
||||
const text =
|
||||
"Details: #recall:extraInfo/fallback:ValuenbspWithnbspSpaces# and #recall:anotherInfo/fallback:Default#";
|
||||
const expected = "Details: Value With Spaces and Default";
|
||||
"Details: #recall:extraInfo/fallback:Value With Spaces# and #recall:anotherInfo/fallback:Default#";
|
||||
const expected = "Details: Value& ;With& ;Spaces and Default";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
test("should handle fallback with only 'nbsp'", () => {
|
||||
const text = "Note: #recall:note/fallback:nbsp#.";
|
||||
const expected = "Note: .";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
test("should handle fallback with only ' '", () => {
|
||||
const text = "Note: #recall:note/fallback: #.";
|
||||
const expected = "Note: & ;.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
|
||||
test("should handle fallback with '$nbsp;' (should not replace '$nbsp;')", () => {
|
||||
const text = "Note: #recall:note/fallback:$nbsp;#.";
|
||||
const expected = "Note: $ ;.";
|
||||
expect(replaceRecallInfo(text, responseData, variables)).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -151,7 +163,7 @@ describe("parseRecallInformation", () => {
|
||||
// other necessary TSurveyQuestion fields can be added here with default values
|
||||
};
|
||||
|
||||
it("should replace recall info in headline", () => {
|
||||
test("should replace recall info in headline", () => {
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
headline: { en: "Welcome, #recall:name/fallback:Guest#!" },
|
||||
@@ -161,7 +173,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.headline.en).toBe(expectedHeadline);
|
||||
});
|
||||
|
||||
it("should replace recall info in subheader", () => {
|
||||
test("should replace recall info in subheader", () => {
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
headline: { en: "Main Question" },
|
||||
@@ -172,7 +184,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.subheader?.en).toBe(expectedSubheader);
|
||||
});
|
||||
|
||||
it("should replace recall info in both headline and subheader", () => {
|
||||
test("should replace recall info in both headline and subheader", () => {
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
headline: { en: "User: #recall:name/fallback:User#" },
|
||||
@@ -183,7 +195,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.subheader?.en).toBe("Survey: Onboarding");
|
||||
});
|
||||
|
||||
it("should not change text if no recall info is present", () => {
|
||||
test("should not change text if no recall info is present", () => {
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
headline: { en: "A simple question." },
|
||||
@@ -199,7 +211,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.subheader?.en).toBe(question.subheader?.en);
|
||||
});
|
||||
|
||||
it("should handle undefined subheader gracefully", () => {
|
||||
test("should handle undefined subheader gracefully", () => {
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
headline: { en: "Question with #recall:name/fallback:User#" },
|
||||
@@ -210,7 +222,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.subheader).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not modify subheader if languageCode content is missing, even if recall is in other lang", () => {
|
||||
test("should not modify subheader if languageCode content is missing, even if recall is in other lang", () => {
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
headline: { en: "Hello #recall:name/fallback:User#" },
|
||||
@@ -222,7 +234,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.subheader?.fr).toBe("Bonjour #recall:name/fallback:Utilisateur#");
|
||||
});
|
||||
|
||||
it("should handle malformed recall string (empty ID) leading to no replacement for that pattern", () => {
|
||||
test("should handle malformed recall string (empty ID) leading to no replacement for that pattern", () => {
|
||||
// This tests extractId returning null because extractRecallInfo won't match '#recall:/fallback:foo#'
|
||||
// due to idPattern requiring at least one char for ID.
|
||||
const question: TSurveyQuestion = {
|
||||
@@ -233,7 +245,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.headline.en).toBe("Malformed: #recall:/fallback:foo# and valid: John Doe");
|
||||
});
|
||||
|
||||
it("should use empty string for empty fallback value", () => {
|
||||
test("should use empty string for empty fallback value", () => {
|
||||
// This tests extractFallbackValue returning ""
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
@@ -243,7 +255,7 @@ describe("parseRecallInformation", () => {
|
||||
expect(result.headline.en).toBe("Data: "); // nonExistentData not found, empty fallback used
|
||||
});
|
||||
|
||||
it("should handle recall info if subheader is present but no text for languageCode", () => {
|
||||
test("should handle recall info if subheader is present but no text for languageCode", () => {
|
||||
const question: TSurveyQuestion = {
|
||||
...baseQuestion,
|
||||
headline: { en: "Headline #recall:name/fallback:User#" },
|
||||
|
||||
@@ -7,27 +7,19 @@ import { type TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
const extractId = (text: string): string | null => {
|
||||
const pattern = /#recall:([A-Za-z0-9_-]+)/;
|
||||
const match = text.match(pattern);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return match?.[1] ?? null;
|
||||
};
|
||||
|
||||
// Extracts the fallback value from a string containing the "fallback" pattern.
|
||||
const extractFallbackValue = (text: string): string => {
|
||||
const pattern = /fallback:(\S*)#/;
|
||||
const match = text.match(pattern);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
return match?.[1] ?? "";
|
||||
};
|
||||
|
||||
// Extracts the complete recall information (ID and fallback) from a headline string.
|
||||
const extractRecallInfo = (headline: string, id?: string): string | null => {
|
||||
const idPattern = id ? id : "[A-Za-z0-9_-]+";
|
||||
const idPattern = id ?? "[A-Za-z0-9_-]+";
|
||||
const pattern = new RegExp(`#recall:(${idPattern})\\/fallback:(\\S*)#`);
|
||||
const match = headline.match(pattern);
|
||||
return match ? match[0] : null;
|
||||
@@ -47,7 +39,7 @@ export const replaceRecallInfo = (
|
||||
const recallItemId = extractId(recallInfo);
|
||||
if (!recallItemId) return modifiedText; // Return the text if no ID could be extracted
|
||||
|
||||
const fallback = extractFallbackValue(recallInfo).replaceAll("nbsp", " ");
|
||||
const fallback = extractFallbackValue(recallInfo).replace(/nbsp/g, " ").trim();
|
||||
let value: string | null = null;
|
||||
|
||||
// Fetching value from variables based on recallItemId
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import preact from "@preact/preset-vite";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, resolve } from "path";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import { loadEnv } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { copyCompiledAssetsPlugin } from "../vite-plugins/copy-compiled-assets";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -29,7 +30,7 @@ const config = ({ mode }) => {
|
||||
},
|
||||
},
|
||||
define: {
|
||||
"process.env": env,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
},
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
|
||||
2708
pnpm-lock.yaml
generated
2708
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user