Skip to content

Commit 28ff809

Browse files
feat: add startsWith operator to routing forms (calcom#23026)
* feat: add startsWith operator to routing forms - Implement startsWith operator in BasicConfig.ts for RAQB - Add custom JsonLogic operation for startsWith evaluation - Add startsWith support to jsonLogicToPrisma for reporting - Supports both form field response routing and attribute routing - Uses case-insensitive matching via normalize function Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * test: add comprehensive unit tests for startsWith operator - Add tests for operator availability in FormFieldsBaseConfig and AttributesBaseConfig - Add tests for JsonLogic configuration validation - Add tests for text widget operator integration - All 15 tests pass including new startsWith operator tests Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * test: move startsWith operator tests to jsonLogic.test.ts - Add comprehensive startsWith tests to packages/lib/raqb/jsonLogic.test.ts - Remove startsWith tests from packages/app-store/routing-forms/__tests__/config.test.ts - Tests verify case-insensitive matching, edge cases, and falsy value handling - All tests pass: jsonLogic.test.ts (18 passed) and config.test.ts (12 passed) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: remove unused startsWith operator from jsonLogicToPrisma.ts - Remove startsWith entry from OPERATOR_MAP in jsonLogicToPrisma.ts - File is currently unused so this cleanup keeps PR focused - Core startsWith functionality remains intact in jsonLogic.ts and BasicConfig.ts Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * test: add comprehensive edge case tests for startsWith operator - Add test cases for non-string first argument (haystack) handling - Cover null, undefined, numbers, booleans, arrays, and objects - Update startsWith implementation to safely handle non-string inputs - Ensure operation returns false without throwing runtime errors - All tests now pass: 19 passed | 1 skipped Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: improve startsWith operation with type guards and String.prototype.startsWith - Change argument types from string to unknown for better type safety - Add type guards to return false when either operand is not a string - Use String.prototype.startsWith instead of indexOf for cleaner implementation - Keep existing empty-second check and normalize function calls - All tests continue to pass: 19 passed | 1 skipped Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 8e73421 commit 28ff809

3 files changed

Lines changed: 53 additions & 9 deletions

File tree

packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,12 @@ const operators: Operators = {
9696
labelForFormat: "Not Contains",
9797
valueSources: ["value"],
9898
},
99-
/**
100-
* Not supported with JSONLogic. Implement them and add these back -> https://github.com/jwadhams/json-logic-js/issues/81
101-
*/
102-
// starts_with: {
103-
// label: "Starts with",
104-
// labelForFormat: "Starts with",
105-
// jsonLogic: undefined, // not supported
106-
// valueSources: ["value"],
107-
// },
99+
starts_with: {
100+
label: "Starts with",
101+
labelForFormat: "Starts with",
102+
jsonLogic: "starts_with",
103+
valueSources: ["value"],
104+
},
108105
// ends_with: {
109106
// label: "Ends with",
110107
// labelForFormat: "Ends with",

packages/lib/raqb/jsonLogic.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,37 @@ describe("jsonLogic", () => {
225225
).toBe(false);
226226
});
227227
});
228+
229+
describe("starts_with operation", () => {
230+
it("should return true if string starts with the given substring case-insensitively", () => {
231+
expect(jsonLogic.apply({ starts_with: ["Hello World", "hello"] })).toBe(true);
232+
expect(jsonLogic.apply({ starts_with: ["HELLO WORLD", "hello"] })).toBe(true);
233+
expect(jsonLogic.apply({ starts_with: ["hello", "HELLO"] })).toBe(true);
234+
});
235+
236+
it("should return false if string does not start with the given substring", () => {
237+
expect(jsonLogic.apply({ starts_with: ["Hello World", "world"] })).toBe(false);
238+
expect(jsonLogic.apply({ starts_with: ["Hello World", "hi"] })).toBe(false);
239+
});
240+
241+
it("should return false if the second argument is falsy", () => {
242+
expect(jsonLogic.apply({ starts_with: ["Hello World", ""] })).toBe(false);
243+
expect(jsonLogic.apply({ starts_with: ["Hello World", null] })).toBe(false);
244+
expect(jsonLogic.apply({ starts_with: ["Hello World", false] })).toBe(false);
245+
});
246+
247+
it("should handle edge cases", () => {
248+
expect(jsonLogic.apply({ starts_with: ["", "hello"] })).toBe(false);
249+
expect(jsonLogic.apply({ starts_with: ["hello", "hello world"] })).toBe(false);
250+
});
251+
252+
it("should handle non-string first argument (haystack) safely", () => {
253+
expect(jsonLogic.apply({ starts_with: [null, "x"] })).toBe(false);
254+
expect(jsonLogic.apply({ starts_with: [undefined, "x"] })).toBe(false);
255+
expect(jsonLogic.apply({ starts_with: [123, "1"] })).toBe(false);
256+
expect(jsonLogic.apply({ starts_with: [true, "t"] })).toBe(false);
257+
expect(jsonLogic.apply({ starts_with: [[], ""] })).toBe(false);
258+
expect(jsonLogic.apply({ starts_with: [{}, ""] })).toBe(false);
259+
});
260+
});
228261
});

packages/lib/raqb/jsonLogic.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,18 @@ jsonLogic.add_operation("in", function (a: string, b: string | string[]) {
5050
return second.indexOf(first) !== -1;
5151
});
5252

53+
/**
54+
* Short Text/Long Text "starts with" uses it
55+
*/
56+
jsonLogic.add_operation("starts_with", function (a: unknown, b: unknown) {
57+
if (typeof a !== "string" || typeof b !== "string") return false;
58+
59+
const first = normalize(a);
60+
const second = normalize(b);
61+
62+
if (!second) return false;
63+
64+
return first.startsWith(second);
65+
});
66+
5367
export default jsonLogic;

0 commit comments

Comments
 (0)