Compare commits

...

4 Commits

Author SHA1 Message Date
Cursor Agent
6fb754bf87 chore: update pnpm-lock.yaml for vitest dependency 2026-02-13 01:25:01 +00:00
Cursor Agent
e1cdbf7bc5 style: use test instead of it in validation tests
- Fixed linting issues by following @vitest/consistent-test-it rule
- Changed all 'it' to 'test' for consistency
2026-02-13 01:24:28 +00:00
Cursor Agent
b52f1a19d4 test: add comprehensive tests for findLanguageCodesForDuplicateLabels
- Added test coverage for undefined value handling
- Tests verify fix for TypeError when language keys are missing
- Added test script to types package.json
- All 7 tests passing
2026-02-13 01:21:21 +00:00
Cursor Agent
f55b9de3a2 fix: prevent TypeError when accessing undefined TI18nString values in findLanguageCodesForDuplicateLabels
Fixes FORMBRICKS-JM

- Updated findLanguageCodesForDuplicateLabels in both validation.ts and elements-validation.ts
- Changed to safely filter undefined values before calling .trim()
- Now filters for string type first, then trims, then filters empty strings
- Prevents 'undefined is not an object (evaluating e[r].trim)' error when survey labels are incomplete
2026-02-13 01:19:23 +00:00
5 changed files with 283 additions and 5 deletions

View File

@@ -6,7 +6,9 @@
"sideEffects": false,
"scripts": {
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
"clean": "rimraf node_modules .turbo"
"clean": "rimraf node_modules .turbo",
"test": "vitest run",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@prisma/client": "6.14.0",
@@ -16,6 +18,7 @@
},
"devDependencies": {
"@formbricks/config-typescript": "workspace:*",
"@formbricks/database": "workspace:*"
"@formbricks/database": "workspace:*",
"vitest": "3.0.1"
}
}

View File

@@ -104,8 +104,9 @@ export const findLanguageCodesForDuplicateLabels = (
for (const language of languagesToCheck) {
const labelTexts = labels
.map((label) => label[language])
.filter((text): text is string => typeof text === "string" && text.trim().length > 0)
.map((text) => text.trim());
.filter((text): text is string => typeof text === "string")
.map((text) => text.trim())
.filter((text) => text.length > 0);
const uniqueLabels = new Set(labelTexts);
if (uniqueLabels.size !== labelTexts.length) {

View File

@@ -0,0 +1,108 @@
import { describe, expect, test } from "vitest";
import type { TI18nString } from "../i18n";
import type { TSurveyLanguage } from "./types";
import { findLanguageCodesForDuplicateLabels } from "./validation";
describe("findLanguageCodesForDuplicateLabels", () => {
const mockLanguages: TSurveyLanguage[] = [
{
default: true,
enabled: true,
language: { id: "en", code: "en", alias: null, createdAt: new Date(), updatedAt: new Date() },
},
{
default: false,
enabled: true,
language: { id: "es", code: "es", alias: null, createdAt: new Date(), updatedAt: new Date() },
},
];
test("should handle undefined language values without throwing", () => {
const labels: TI18nString[] = [
{ default: "Option 1", es: "Opción 1" },
{ default: "Option 2" }, // Missing 'es' key - this was causing the error
{ default: "Option 3", es: "Opción 3" },
];
// This should not throw a TypeError
expect(() => findLanguageCodesForDuplicateLabels(labels, mockLanguages)).not.toThrow();
});
test("should detect duplicates when labels are the same", () => {
const labels: TI18nString[] = [
{ default: "Option 1", es: "Opción 1" },
{ default: "Option 1", es: "Opción 1" }, // Duplicate
{ default: "Option 3", es: "Opción 3" },
];
const result = findLanguageCodesForDuplicateLabels(labels, mockLanguages);
expect(result).toContain("default");
expect(result).toContain("es");
});
test("should not detect duplicates when labels are different", () => {
const labels: TI18nString[] = [
{ default: "Option 1", es: "Opción 1" },
{ default: "Option 2", es: "Opción 2" },
{ default: "Option 3", es: "Opción 3" },
];
const result = findLanguageCodesForDuplicateLabels(labels, mockLanguages);
expect(result).toHaveLength(0);
});
test("should ignore empty strings when detecting duplicates", () => {
const labels: TI18nString[] = [
{ default: "Option 1", es: "Opción 1" },
{ default: "", es: "" }, // Empty strings
{ default: " ", es: " " }, // Whitespace-only strings
{ default: "Option 2", es: "Opción 2" },
];
const result = findLanguageCodesForDuplicateLabels(labels, mockLanguages);
expect(result).toHaveLength(0);
});
test("should handle mixed scenarios with undefined and duplicates", () => {
const labels: TI18nString[] = [
{ default: "Option 1" }, // Missing 'es'
{ default: "Option 2", es: "Opción" },
{ default: "Option 2", es: "Opción" }, // Duplicate in both languages
];
const result = findLanguageCodesForDuplicateLabels(labels, mockLanguages);
expect(result).toContain("default");
expect(result).toContain("es");
});
test("should work with only default language enabled", () => {
const defaultOnlyLanguages: TSurveyLanguage[] = [
{
default: true,
enabled: true,
language: { id: "en", code: "en", alias: null, createdAt: new Date(), updatedAt: new Date() },
},
];
const labels: TI18nString[] = [
{ default: "Option 1" },
{ default: "Option 1" }, // Duplicate
];
const result = findLanguageCodesForDuplicateLabels(labels, defaultOnlyLanguages);
expect(result).toContain("default");
});
test("should handle completely undefined values gracefully", () => {
const labels: TI18nString[] = [
{ default: "Option 1" },
{ default: "Option 2" },
{} as TI18nString, // Empty object - all languages undefined
];
// This should not throw
expect(() => findLanguageCodesForDuplicateLabels(labels, mockLanguages)).not.toThrow();
const result = findLanguageCodesForDuplicateLabels(labels, mockLanguages);
expect(result).toHaveLength(0);
});
});

View File

@@ -224,7 +224,11 @@ export const findLanguageCodesForDuplicateLabels = (
const duplicateLabels = new Set<string>();
for (const language of languagesToCheck) {
const labelTexts = labels.map((label) => label[language].trim()).filter(Boolean);
const labelTexts = labels
.map((label) => label[language])
.filter((text): text is string => typeof text === "string")
.map((text) => text.trim())
.filter((text) => text.length > 0);
const uniqueLabels = new Set(labelTexts);
if (uniqueLabels.size !== labelTexts.length) {

162
pnpm-lock.yaml generated
View File

@@ -1078,6 +1078,9 @@ importers:
'@formbricks/database':
specifier: workspace:*
version: link:../database
vitest:
specifier: 3.0.1
version: 3.0.1(@types/node@22.15.18)(jiti@2.6.1)(jsdom@27.3.0)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
packages/vite-plugins:
devDependencies:
@@ -6384,12 +6387,26 @@ packages:
vitest:
optional: true
'@vitest/expect@3.0.1':
resolution: {integrity: sha512-oPrXe8dwvQdzUxQFWwibY97/smQ6k8iPVeSf09KEvU1yWzu40G6naHExY0lUgjnTPWMRGQOJnhMBb8lBu48feg==}
'@vitest/expect@3.1.3':
resolution: {integrity: sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==}
'@vitest/expect@3.2.4':
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
'@vitest/mocker@3.0.1':
resolution: {integrity: sha512-5letLsVdFhReCPws/SNwyekBCyi4w2IusycV4T7eVdt2mfellS2yKDrEmnE5KPCHr0Ez5xCZVJbJws3ckuNNgQ==}
peerDependencies:
msw: ^2.4.9
vite: ^5.0.0 || ^6.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/mocker@3.1.3':
resolution: {integrity: sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==}
peerDependencies:
@@ -6412,24 +6429,39 @@ packages:
vite:
optional: true
'@vitest/pretty-format@3.0.1':
resolution: {integrity: sha512-FnyGQ9eFJ/Dnqg3jCvq9O6noXtxbZhOlSvNLZsCGJxhsGiZ5LDepmsTCizRfyGJt4Q6pJmZtx7rO/qqr9R9gDA==}
'@vitest/pretty-format@3.1.3':
resolution: {integrity: sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==}
'@vitest/pretty-format@3.2.4':
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
'@vitest/runner@3.0.1':
resolution: {integrity: sha512-LfVbbYOduTVx8PnYFGH98jpgubHBefIppbPQJBSlgjnRRlaX/KR6J46htECUHpf+ElJZ4xxssAfEz/Cb2iIMYA==}
'@vitest/runner@3.1.3':
resolution: {integrity: sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==}
'@vitest/snapshot@3.0.1':
resolution: {integrity: sha512-ZYV+iw2lGyc4QY2xt61b7Y3NJhSAO7UWcYWMcV0UnMrkXa8hXtfZES6WAk4g7Jr3p4qJm1P0cgDcOFyY5me+Ug==}
'@vitest/snapshot@3.1.3':
resolution: {integrity: sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==}
'@vitest/spy@3.0.1':
resolution: {integrity: sha512-HnGJB3JFflnlka4u7aD0CfqrEtX3FgNaZAar18/KIhfo0r/WADn9PhBfiqAmNw4R/xaRcLzLPFXDwEQV1vHlJA==}
'@vitest/spy@3.1.3':
resolution: {integrity: sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==}
'@vitest/spy@3.2.4':
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
'@vitest/utils@3.0.1':
resolution: {integrity: sha512-i+Gm61rfIeSitPUsu4ZcWqucfb18ShAanRpOG6KlXfd1j6JVK5XxO2Z6lEmfjMnAQRIvvLtJ3JByzDTv347e8w==}
'@vitest/utils@3.1.3':
resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==}
@@ -11343,6 +11375,11 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
vite-node@3.0.1:
resolution: {integrity: sha512-PoH9mCNsSZQXl3gdymM5IE4WR0k0WbnFd89nAyyDvltF2jVGdFcI8vpB1PBdKTcjAR7kkYiHSlIO68X/UT8Q1A==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
vite-node@3.1.3:
resolution: {integrity: sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -11456,6 +11493,31 @@ packages:
typescript: 3.x || 4.x || 5.x
vitest: '>=3.0.0'
vitest@3.0.1:
resolution: {integrity: sha512-SWKoSAkxtFHqt8biR3eN53dzmeWkigEpyipqfblcsoAghVvoFMpxQEj0gc7AajMi6Ra49fjcTN6v4AxklmS4aQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
'@vitest/browser': 3.0.1
'@vitest/ui': 3.0.1
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@types/node':
optional: true
'@vitest/browser':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
vitest@3.1.3:
resolution: {integrity: sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -18907,6 +18969,13 @@ snapshots:
typescript: 5.8.3
vitest: 3.1.3(@types/node@22.15.18)(jiti@2.6.1)(jsdom@27.3.0)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
'@vitest/expect@3.0.1':
dependencies:
'@vitest/spy': 3.0.1
'@vitest/utils': 3.0.1
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/expect@3.1.3':
dependencies:
'@vitest/spy': 3.1.3
@@ -18922,6 +18991,14 @@ snapshots:
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/mocker@3.0.1(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 3.0.1
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
'@vitest/mocker@3.1.3(vite@6.4.1(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 3.1.3
@@ -18946,6 +19023,10 @@ snapshots:
optionalDependencies:
vite: 7.3.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
'@vitest/pretty-format@3.0.1':
dependencies:
tinyrainbow: 2.0.0
'@vitest/pretty-format@3.1.3':
dependencies:
tinyrainbow: 2.0.0
@@ -18954,17 +19035,32 @@ snapshots:
dependencies:
tinyrainbow: 2.0.0
'@vitest/runner@3.0.1':
dependencies:
'@vitest/utils': 3.0.1
pathe: 2.0.3
'@vitest/runner@3.1.3':
dependencies:
'@vitest/utils': 3.1.3
pathe: 2.0.3
'@vitest/snapshot@3.0.1':
dependencies:
'@vitest/pretty-format': 3.0.1
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/snapshot@3.1.3':
dependencies:
'@vitest/pretty-format': 3.1.3
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@3.0.1':
dependencies:
tinyspy: 3.0.2
'@vitest/spy@3.1.3':
dependencies:
tinyspy: 3.0.2
@@ -18973,6 +19069,12 @@ snapshots:
dependencies:
tinyspy: 4.0.4
'@vitest/utils@3.0.1':
dependencies:
'@vitest/pretty-format': 3.0.1
loupe: 3.2.1
tinyrainbow: 2.0.0
'@vitest/utils@3.1.3':
dependencies:
'@vitest/pretty-format': 3.1.3
@@ -24531,6 +24633,27 @@ snapshots:
vary@1.1.2: {}
vite-node@3.0.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vite-node@3.1.3(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2):
dependencies:
cac: 6.7.14
@@ -24717,6 +24840,45 @@ snapshots:
typescript: 5.8.3
vitest: 3.1.3(@types/node@22.15.18)(jiti@2.4.2)(jsdom@27.3.0)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
vitest@3.0.1(@types/node@22.15.18)(jiti@2.6.1)(jsdom@27.3.0)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2):
dependencies:
'@vitest/expect': 3.0.1
'@vitest/mocker': 3.0.1(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.0.1
'@vitest/snapshot': 3.0.1
'@vitest/spy': 3.0.1
'@vitest/utils': 3.0.1
chai: 5.3.3
debug: 4.4.3
expect-type: 1.3.0
magic-string: 0.30.21
pathe: 2.0.3
std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
vite-node: 3.0.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.15.18
jsdom: 27.3.0
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vitest@3.1.3(@types/node@22.15.18)(jiti@2.4.2)(jsdom@27.3.0)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2):
dependencies:
'@vitest/expect': 3.1.3