diff --git a/src/lib/interaction-step-helpers.spec.ts b/src/lib/interaction-step-helpers.spec.ts index c3446d852..c89439b4b 100644 --- a/src/lib/interaction-step-helpers.spec.ts +++ b/src/lib/interaction-step-helpers.spec.ts @@ -1,7 +1,7 @@ import type { InteractionStep } from "../api/interaction-step"; import { makeTree } from "./interaction-step-helpers"; -const baseEmptyStep = { +const baseEmptyStep: Omit = { questionText: "", answerActions: "", scriptOptions: [""], @@ -10,187 +10,93 @@ const baseEmptyStep = { createdAt: "2021-01-26T00:00:00Z" }; +const step = ( + id: string, + parentInteractionId: string | null, + overrides: Partial = {} +): InteractionStep => ({ + ...baseEmptyStep, + id, + parentInteractionId, + createdAt: `2021-01-26T00:00:0${id}Z`, + ...overrides +}); + describe("makeTree", () => { - test("handles empty interaction steps argument", () => { - const steps: InteractionStep[] = []; - const tree = makeTree(steps); + it("returns a wrapper with empty children for an empty input", () => { + const tree = makeTree([]); expect(tree).toEqual({ interactionSteps: [] }); }); - test("handles building a basic tree", () => { - const rootStep: InteractionStep = { - ...baseEmptyStep, - id: "1", - parentInteractionId: null, - createdAt: "2021-01-26T00:00:01Z" - }; - const childStepA: InteractionStep = { - ...baseEmptyStep, - id: "2", - parentInteractionId: "1", - createdAt: "2021-01-26T00:00:02Z" - }; - const childStepB: InteractionStep = { - ...baseEmptyStep, - id: "3", - parentInteractionId: "1", - createdAt: "2021-01-26T00:00:03Z" - }; - const childStepAA: InteractionStep = { - ...baseEmptyStep, - id: "4", - parentInteractionId: "2", - createdAt: "2021-01-26T00:00:04Z" - }; - const steps: InteractionStep[] = [ - rootStep, - childStepA, - childStepB, - childStepAA - ]; + it("builds a tree with nested children", () => { + const root = step("1", null); + const childA = step("2", "1"); + const childB = step("3", "1"); + const grandchild = step("4", "2"); - const tree = makeTree(steps); + const tree = makeTree([root, childA, childB, grandchild]); expect(tree).toEqual({ - ...rootStep, + ...root, interactionSteps: [ - { ...childStepB, interactionSteps: [] }, + { ...childB, interactionSteps: [] }, { - ...childStepA, - interactionSteps: [{ ...childStepAA, interactionSteps: [] }] + ...childA, + interactionSteps: [{ ...grandchild, interactionSteps: [] }] } ] }); }); - test("handles building a tree with deleted steps", () => { - const rootStep: InteractionStep = { - ...baseEmptyStep, - id: "1", - parentInteractionId: null, - createdAt: "2021-01-26T00:00:01Z" - }; - const childStepA: InteractionStep = { - ...baseEmptyStep, - id: "2", - parentInteractionId: "1", - createdAt: "2021-01-26T00:00:02Z" - }; - const childStepB: InteractionStep = { - ...baseEmptyStep, - id: "3", - parentInteractionId: "1", - isDeleted: true, - createdAt: "2021-01-26T00:00:03Z" - }; - const childStepAA: InteractionStep = { - ...baseEmptyStep, - id: "4", - parentInteractionId: "2", - createdAt: "2021-01-26T00:00:04Z" - }; - const steps: InteractionStep[] = [ - rootStep, - childStepA, - childStepB, - childStepAA - ]; + it("includes deleted steps in the tree", () => { + const root = step("1", null); + const childA = step("2", "1"); + const deletedChild = step("3", "1", { isDeleted: true }); + const grandchild = step("4", "2"); - const tree = makeTree(steps); + const tree = makeTree([root, childA, deletedChild, grandchild]); expect(tree).toEqual({ - ...rootStep, + ...root, interactionSteps: [ - { ...childStepB, interactionSteps: [] }, + { ...deletedChild, interactionSteps: [] }, { - ...childStepA, - interactionSteps: [{ ...childStepAA, interactionSteps: [] }] + ...childA, + interactionSteps: [{ ...grandchild, interactionSteps: [] }] } ] }); }); - test("handles building a tree when input array is unsorted", () => { - const rootStep: InteractionStep = { - ...baseEmptyStep, - id: "1", - parentInteractionId: null, - createdAt: "2021-01-26T00:00:01Z" - }; - const childStepA: InteractionStep = { - ...baseEmptyStep, - id: "2", - parentInteractionId: "1", - createdAt: "2021-01-26T00:00:02Z" - }; - const childStepB: InteractionStep = { - ...baseEmptyStep, - id: "3", - parentInteractionId: "1", - createdAt: "2021-01-26T00:00:03Z" - }; - const childStepC: InteractionStep = { - ...baseEmptyStep, - id: "4", - parentInteractionId: "1", - createdAt: "2021-01-26T00:00:04Z" - }; - const steps: InteractionStep[] = [ - rootStep, - childStepA, - childStepC, - childStepB - ]; + it("sorts children by createdAt (newest first)", () => { + const root = step("1", null); + const childA = step("2", "1"); + const childB = step("3", "1"); + const childC = step("4", "1"); - const tree = makeTree(steps); + const tree = makeTree([root, childA, childC, childB]); expect(tree).toEqual({ - ...rootStep, + ...root, interactionSteps: [ - { ...childStepC, interactionSteps: [] }, - { ...childStepB, interactionSteps: [] }, - { ...childStepA, interactionSteps: [] } + { ...childC, interactionSteps: [] }, + { ...childB, interactionSteps: [] }, + { ...childA, interactionSteps: [] } ] }); }); - test("handles building a tree when given multiple root interaction steps", () => { - const rootStepA: InteractionStep = { - ...baseEmptyStep, - id: "1", - parentInteractionId: null, - createdAt: "2021-01-26T00:00:01Z" - }; - const childStepAA: InteractionStep = { - ...baseEmptyStep, - id: "2", - parentInteractionId: "1", - createdAt: "2021-01-26T00:00:02Z" - }; - const rootStepB: InteractionStep = { - ...baseEmptyStep, - id: "3", - parentInteractionId: null, - createdAt: "2021-01-26T00:00:03Z" - }; - const childStepBA: InteractionStep = { - ...baseEmptyStep, - id: "4", - parentInteractionId: "3", - createdAt: "2021-01-26T00:00:04Z" - }; + it("uses the newest root when multiple roots exist", () => { + const rootA = step("1", null); + const childAA = step("2", "1"); + const rootB = step("3", null); + const childBA = step("4", "3"); - const steps: InteractionStep[] = [ - rootStepA, - childStepAA, - rootStepB, - childStepBA - ]; - const tree = makeTree(steps); + const tree = makeTree([rootA, childAA, rootB, childBA]); expect(tree).toEqual({ - ...rootStepB, - interactionSteps: [{ ...childStepBA, interactionSteps: [] }] + ...rootB, + interactionSteps: [{ ...childBA, interactionSteps: [] }] }); }); }); diff --git a/src/lib/scripts.spec.ts b/src/lib/scripts.spec.ts index e62702092..efff5b7b3 100644 --- a/src/lib/scripts.spec.ts +++ b/src/lib/scripts.spec.ts @@ -1,26 +1,31 @@ import { titleCase } from "./scripts"; -describe("script utilities", () => { - it("converts uppercase single word to title case", () => { - expect(titleCase("SPOKE")).toEqual("Spoke"); +describe("titleCase", () => { + it("converts an uppercase single word", () => { + expect(titleCase("SPOKE")).toBe("Spoke"); }); - it("converts lowercase single word to title case", () => { - expect(titleCase("spoke")).toEqual("Spoke"); + + it("converts a lowercase single word", () => { + expect(titleCase("spoke")).toBe("Spoke"); }); - it("converts mixed-case single word to title case", () => { - expect(titleCase("sPoKe")).toEqual("Spoke"); + + it("converts a mixed-case single word", () => { + expect(titleCase("sPoKe")).toBe("Spoke"); }); - it("converts uppercase words to title case", () => { - expect(titleCase("SPOKE REWIRED")).toEqual("Spoke Rewired"); + it("converts multiple uppercase words", () => { + expect(titleCase("SPOKE REWIRED")).toBe("Spoke Rewired"); }); - it("converts lowercase words to title case", () => { - expect(titleCase("spoke rewired")).toEqual("Spoke Rewired"); + + it("converts multiple lowercase words", () => { + expect(titleCase("spoke rewired")).toBe("Spoke Rewired"); }); - it("converts mixed-case words to title case", () => { - expect(titleCase("sPoKe ReWirEd")).toEqual("Spoke Rewired"); + + it("converts multiple mixed-case words", () => { + expect(titleCase("sPoKe ReWirEd")).toBe("Spoke Rewired"); }); - it("ignores hyphens", () => { - expect(titleCase("spoke-rewired")).toEqual("Spoke-rewired"); + + it("does not split on hyphens", () => { + expect(titleCase("spoke-rewired")).toBe("Spoke-rewired"); }); }); diff --git a/src/lib/tz-helpers.spec.ts b/src/lib/tz-helpers.spec.ts index 2e300b6e9..338c894e4 100644 --- a/src/lib/tz-helpers.spec.ts +++ b/src/lib/tz-helpers.spec.ts @@ -1,22 +1,22 @@ import { DateTime } from "./datetime"; import { getSendBeforeUtc } from "./tz-helpers"; -describe("getSendBefore", () => { - test("gets correct sendBefore for midday Eastern", () => { - const middayUsEastern = DateTime.fromISO("2020-01-20T12:00:00-05:00"); - expect(getSendBeforeUtc("America/New_York", 21, middayUsEastern)).toBe( +describe("getSendBeforeUtc", () => { + it("returns end-of-day UTC for a midday local time", () => { + const middayEastern = DateTime.fromISO("2020-01-20T12:00:00-05:00"); + expect(getSendBeforeUtc("America/New_York", 21, middayEastern)).toBe( "2020-01-21T02:00:00.000Z" ); }); - test("gets correct sendBefore for afterhours Eastern", () => { + it("returns next-day end hour when current time is after end hour", () => { const afterHoursEastern = DateTime.fromISO("2020-01-20T22:00:00-05:00"); expect(getSendBeforeUtc("America/New_York", 21, afterHoursEastern)).toBe( "2020-01-21T02:00:00.000Z" ); }); - test("gets correct sendBefore for afterhours UTC", () => { + it("handles UTC input time correctly", () => { const afterHoursUtc = DateTime.fromISO("2020-01-21T03:00:00Z"); expect(getSendBeforeUtc("America/New_York", 21, afterHoursUtc)).toBe( "2020-01-21T02:00:00.000Z" diff --git a/src/lib/utils.spec.ts b/src/lib/utils.spec.ts index e102c3525..8e6fb681f 100644 --- a/src/lib/utils.spec.ts +++ b/src/lib/utils.spec.ts @@ -6,33 +6,33 @@ import { } from "./utils"; describe("stringIsAValidUrl", () => { - test("recognizes valid URL", () => { + it("accepts a valid https URL", () => { expect(stringIsAValidUrl("https://www.politicsrewired.com")).toBe(true); }); - test("recognizes valid URL with query string", () => { + it("accepts a valid URL with query parameters", () => { expect( stringIsAValidUrl("https://www.politicsrewired.com?foo=bar&bar=baz") ).toBe(true); }); - test("rejects invalid URL without scheme", () => { + it("rejects a URL without a scheme", () => { expect(stringIsAValidUrl("www.politicsrewired.com")).toBe(false); }); - test("rejects invalid URL", () => { + it("rejects a relative path", () => { expect(stringIsAValidUrl("foo/bar")).toBe(false); }); }); describe("replaceAll", () => { - test("replaces mulitple occurrences", () => { + it("replaces multiple occurrences of a substring", () => { expect( replaceAll("buffalo buffalo buffalo buffalo buffalo", "buffalo", "squid") ).toBe("squid squid squid squid squid"); }); - test("escapes special regex characters", () => { + it("escapes special regex characters in the search string", () => { expect(replaceAll(`what about \\ characters?`, `\\`, `?`)).toBe( "what about ? characters?" ); @@ -40,21 +40,21 @@ describe("replaceAll", () => { }); describe("asPercent", () => { - test("handles 0 denominator correctly", () => { + it("returns 0 when denominator is 0", () => { expect(asPercent(10, 0)).toBe(0); }); - test("handles 100% correctly", () => { + it("returns 100 for equal numerator and denominator", () => { expect(asPercent(10, 10)).toBe(100); }); }); describe("asPercentWithTotal", () => { - test("handles 0 denominator correctly", () => { + it("returns 0% with total when denominator is 0", () => { expect(asPercentWithTotal(10, 0)).toBe("0%(10)"); }); - test("truncates decimal", () => { + it("truncates decimal to 4 characters", () => { expect(asPercentWithTotal(9, 11)).toBe("81.8%(9)"); }); }); diff --git a/src/lib/zip-format.spec.ts b/src/lib/zip-format.spec.ts index add080618..281fbecb0 100644 --- a/src/lib/zip-format.spec.ts +++ b/src/lib/zip-format.spec.ts @@ -1,67 +1,70 @@ import { getFormattedZip, zipToTimeZone } from "./zip-format"; -describe("test getFormattedZip", () => { - it("handles zip correctly", () => { - expect(getFormattedZip("12345")).toEqual("12345"); +describe("getFormattedZip", () => { + it("formats a standard 5-digit zip", () => { + expect(getFormattedZip("12345")).toBe("12345"); }); - it("handles zip + 4 correctly", () => { - expect(getFormattedZip("12345-3456")).toEqual("12345"); + it("extracts the 5-digit portion from a zip+4", () => { + expect(getFormattedZip("12345-3456")).toBe("12345"); }); - it("handles malformed zip correctly 1", () => { - expect(getFormattedZip("12345-abcd")).toEqual("12345"); + it("extracts the 5-digit portion ignoring a malformed suffix", () => { + expect(getFormattedZip("12345-abcd")).toBe("12345"); }); // TODO(PR 4): These tests document the correct expected behavior but // fail because of a bug in getFormattedZip — the 4-digit regex matches // substrings instead of requiring a pure 4-digit input. Fix in PR 4. - it.skip("handles malformed zip correctly 2", () => { - expect(getFormattedZip("a2345-abcd")).toBeFalsy(); + it.skip("returns null for input with embedded 4-digit sequence", () => { + expect(getFormattedZip("a2345-abcd")).toBeNull(); }); - it.skip("handles malformed zip correctly 3", () => { - expect(getFormattedZip("2345-abcd")).toBeFalsy(); + it.skip("returns null for 4-digit zip with non-numeric suffix", () => { + expect(getFormattedZip("2345-abcd")).toBeNull(); }); - const wrapper = () => { - getFormattedZip("11790", "OZ"); - }; - - it("handles not the USA correctly", () => { - expect(wrapper).toThrow(/OZ/); + it("throws for a non-US country", () => { + expect(() => getFormattedZip("11790", "OZ")).toThrow(/OZ/); }); }); -describe("test zipToTimeZone", () => { - it("handles string with 2 leading zeroes", () => { +describe("zipToTimeZone", () => { + it("maps a zip with leading zeroes to the correct range", () => { const result = zipToTimeZone("00100"); + expect(result).toBeDefined(); expect(result[0]).toBe(-1); expect(result[1]).toBe(210); expect(result[2]).toBe(-4); expect(result[3]).toBe(1); }); - it("handles 3-digit integer", () => { + + it("accepts a numeric zip input", () => { const result = zipToTimeZone(100); + expect(result).toBeDefined(); expect(result[0]).toBe(-1); expect(result[1]).toBe(210); expect(result[2]).toBe(-4); expect(result[3]).toBe(1); }); - it("handles highest zip in the list", () => { - expect(zipToTimeZone("99501")).toBeFalsy(); + + it("returns undefined for a zip beyond the highest range", () => { + expect(zipToTimeZone("99501")).toBeUndefined(); }); - it("handles a zip at the lower boundary of a range", () => { + + it("matches a zip at the lower boundary of a range", () => { const result = zipToTimeZone("59000"); expect(result[2]).toBe(-7); expect(result[3]).toBe(1); }); - it("handles a zip one lower than the upper limit of a range", () => { + + it("matches a zip one below the upper boundary of a range", () => { const result = zipToTimeZone("69020"); expect(result[2]).toBe(-6); expect(result[3]).toBe(1); }); - it("handles a zip at the upper limit of a range", () => { - expect(zipToTimeZone("69021")).toBeFalsy(); + + it("returns undefined for a zip at the upper boundary (exclusive)", () => { + expect(zipToTimeZone("69021")).toBeUndefined(); }); });