Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 53 additions & 147 deletions src/lib/interaction-step-helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { InteractionStep } from "../api/interaction-step";
import { makeTree } from "./interaction-step-helpers";

const baseEmptyStep = {
const baseEmptyStep: Omit<InteractionStep, "id" | "parentInteractionId"> = {
questionText: "",
answerActions: "",
scriptOptions: [""],
Expand All @@ -10,187 +10,93 @@ const baseEmptyStep = {
createdAt: "2021-01-26T00:00:00Z"
};

const step = (
id: string,
parentInteractionId: string | null,
overrides: Partial<InteractionStep> = {}
): 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: [] }]
});
});
});
35 changes: 20 additions & 15 deletions src/lib/scripts.spec.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
12 changes: 6 additions & 6 deletions src/lib/tz-helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
20 changes: 10 additions & 10 deletions src/lib/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,55 @@ 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?"
);
});
});

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)");
});
});
Loading
Loading