diff --git a/packages/string-templates/src/helpers/constants.ts b/packages/string-templates/src/helpers/constants.ts index ee84a1dc47..fb6bf4e4f5 100644 --- a/packages/string-templates/src/helpers/constants.ts +++ b/packages/string-templates/src/helpers/constants.ts @@ -36,6 +36,7 @@ export const HelperFunctionNames = { ALL: "all", LITERAL: "literal", JS: "js", + DECODE_ID: "decodeId", } export const LITERAL_MARKER = "%LITERAL%" diff --git a/packages/string-templates/src/helpers/index.ts b/packages/string-templates/src/helpers/index.ts index fe74a6d711..ea09c2c545 100644 --- a/packages/string-templates/src/helpers/index.ts +++ b/packages/string-templates/src/helpers/index.ts @@ -25,13 +25,29 @@ function isObject(value: string | any[]) { ) } -const HELPERS = [ +export const HELPERS = [ // external helpers new Helper(HelperFunctionNames.OBJECT, (value: any) => { return new Handlebars.SafeString(JSON.stringify(value)) }), // javascript helper new Helper(HelperFunctionNames.JS, processJS, false), + new Helper(HelperFunctionNames.DECODE_ID, (_id: string | { _id: string }) => { + if (!_id) { + return [] + } + // have to replace on the way back as we swapped out the double quotes + // when encoding, but JSON can't handle the single quotes + const id = typeof _id === "string" ? _id : _id._id + const decoded: string = decodeURIComponent(id).replace(/'/g, '"') + try { + const parsed = JSON.parse(decoded) + return Array.isArray(parsed) ? parsed : [parsed] + } catch (err) { + // wasn't json - likely was handlebars for a many to many + return [_id] + } + }), // this help is applied to all statements new Helper( HelperFunctionNames.ALL, diff --git a/packages/string-templates/test/helpers.spec.ts b/packages/string-templates/test/helpers.spec.ts index 12de4f1c29..7ef09cb2a4 100644 --- a/packages/string-templates/test/helpers.spec.ts +++ b/packages/string-templates/test/helpers.spec.ts @@ -517,3 +517,44 @@ describe("helper overlap", () => { expect(output).toEqual("a") }) }) + +describe("Test the decodeId helper", () => { + it("should decode a valid encoded ID", async () => { + const encodedId = encodeURIComponent("[42]") // "%5B42%5D" + const output = await processString("{{ decodeId id }}", { id: encodedId }) + expect(output).toBe("42") + }) + + it("Should return an unchanged string if the string isn't encoded", async () => { + const unencodedId = "forty-two" + const output = await processString("{{ decodeId id }}", { id: unencodedId }) + expect(output).toBe("forty-two") + }) + + it("Should return a string of comma-separated IDs when passed multiple IDs in a URI encoded array", async () => { + const encodedIds = encodeURIComponent("[1,2,3]") // "%5B1%2C2%2C3%5D" + const output = await processString("{{ decodeId id }}", { id: encodedIds }) + expect(output).toBe("1,2,3") + }) + + it("Handles empty array gracefully", async () => { + const output = await processString("{{ decodeId value }}", { + value: [], + }) + expect(output).toBe("[[]]") + }) + + it("Handles undefined gracefully", async () => { + const output = await processString("{{ decodeId value }}", { + value: undefined, + }) + expect(output).toBe("") + }) + + it("Handles null gracefully", async () => { + const output = await processString("{{ decodeId value }}", { + value: undefined, + }) + expect(output).toBe("") + }) +})