import { describe, it, expect } from "vitest"; import { decodeMessages, encodeMessages, isValidMessagePart, legacyMessageToParts, type MessagePart, } from "./reminder-messages"; describe("isValidMessagePart", () => { it("accepts a non-empty text part", () => { expect( isValidMessagePart({ kind: "text", textContent: "hi", mediaId: null }), ).toBe(true); }); it("rejects an empty/whitespace text part", () => { expect( isValidMessagePart({ kind: "text", textContent: " ", mediaId: null }), ).toBe(false); expect( isValidMessagePart({ kind: "text", textContent: null, mediaId: null }), ).toBe(false); }); it("accepts a media part with mediaId, with or without caption", () => { expect( isValidMessagePart({ kind: "media", textContent: null, mediaId: "uuid" }), ).toBe(true); expect( isValidMessagePart({ kind: "media", textContent: "cap", mediaId: "uuid" }), ).toBe(true); }); it("rejects a media part without mediaId", () => { expect( isValidMessagePart({ kind: "media", textContent: "cap", mediaId: null }), ).toBe(false); }); it("rejects unknown kinds and non-objects", () => { expect(isValidMessagePart({ kind: "weird", mediaId: null, textContent: "" })).toBe(false); expect(isValidMessagePart(null)).toBe(false); expect(isValidMessagePart("hi")).toBe(false); }); }); describe("encodeMessages / decodeMessages round-trip", () => { const sample: MessagePart[] = [ { kind: "text", textContent: "Hello there", mediaId: null }, { kind: "media", textContent: "caption", mediaId: "11111111-1111-1111-1111-111111111111" }, { kind: "media", textContent: null, mediaId: "22222222-2222-2222-2222-222222222222" }, ]; it("round-trips a non-trivial stack", () => { const encoded = encodeMessages(sample); expect(typeof encoded).toBe("string"); expect(decodeMessages(encoded)).toEqual(sample); }); it("survives URL-unsafe characters in text content (newlines, &, =, %, #)", () => { const trickier: MessagePart[] = [ { kind: "text", textContent: "Line 1\nLine 2 & more = special % # values", mediaId: null, }, ]; const encoded = encodeMessages(trickier); // Encoded form must be safely embeddable as a URL param value. expect(encoded).not.toContain("\n"); expect(encoded).not.toContain("&"); expect(decodeMessages(encoded)).toEqual(trickier); }); it("decodeMessages returns null on null/undefined/empty/garbage input", () => { expect(decodeMessages(null)).toBeNull(); expect(decodeMessages(undefined)).toBeNull(); expect(decodeMessages("")).toBeNull(); expect(decodeMessages("not-json")).toBeNull(); expect(decodeMessages(encodeURIComponent("not an array"))).toBeNull(); }); it("decodeMessages drops invalid entries and returns null when nothing valid is left", () => { const mixed = encodeURIComponent( JSON.stringify([ { kind: "text", textContent: "", mediaId: null }, // invalid { kind: "media", textContent: null, mediaId: null }, // invalid ]), ); expect(decodeMessages(mixed)).toBeNull(); }); it("decodeMessages keeps valid entries when some are invalid", () => { const mixed = encodeURIComponent( JSON.stringify([ { kind: "text", textContent: "good", mediaId: null }, { kind: "media", textContent: null, mediaId: null }, // invalid { kind: "media", textContent: null, mediaId: "uuid" }, ]), ); expect(decodeMessages(mixed)).toEqual([ { kind: "text", textContent: "good", mediaId: null }, { kind: "media", textContent: null, mediaId: "uuid" }, ]); }); }); describe("legacyMessageToParts back-compat fallback", () => { it("text-only legacy URL → one text part", () => { expect(legacyMessageToParts("hi", null, null)).toEqual([ { kind: "text", textContent: "hi", mediaId: null }, ]); }); it("media + caption legacy URL → one media part with caption", () => { expect(legacyMessageToParts(null, "uuid", "cap")).toEqual([ { kind: "media", textContent: "cap", mediaId: "uuid" }, ]); }); it("media without caption → media part with null textContent", () => { expect(legacyMessageToParts(null, "uuid", null)).toEqual([ { kind: "media", textContent: null, mediaId: "uuid" }, ]); }); it("media + legacy text (no caption) reuses text as caption", () => { expect(legacyMessageToParts("text-too", "uuid", null)).toEqual([ { kind: "media", textContent: "text-too", mediaId: "uuid" }, ]); }); it("nothing useful returns null", () => { expect(legacyMessageToParts(null, null, null)).toBeNull(); expect(legacyMessageToParts("", null, null)).toBeNull(); expect(legacyMessageToParts(" ", null, null)).toBeNull(); }); });