");
+ expect(result).toContain("code here");
+ expect(result).toContain("");
+ });
+
+ it("parses fenced code blocks", () => {
+ const result = parseMarkdown("```\ncode block\n```");
+ expect(result).toContain("");
+ expect(result).toContain("");
+ expect(result).toContain("code block");
+ });
+
+ it("parses blockquotes", () => {
+ const result = parseMarkdown("> Quote here");
+ expect(result).toContain("");
+ expect(result).toContain("Quote here");
+ expect(result).toContain("
");
+ });
+
+ it("handles mixed markdown elements", () => {
+ const markdown = "# Title\n\n**Bold** and *italic* text\n\n- List item";
+ const result = parseMarkdown(markdown);
+ expect(result).toContain("");
+ expect(result).toContain("");
+ expect(result).toContain("");
+ expect(result).toContain("");
+ });
+
+ it("supports GitHub Flavored Markdown (GFM)", () => {
+ const result = parseMarkdown("~~strikethrough~~");
+ expect(result).toContain("strikethrough");
+ });
+
+ it("handles tables (GFM feature)", () => {
+ const markdown = "| Header |\n| ------ |\n| Cell |";
+ const result = parseMarkdown(markdown);
+ expect(result).toContain("");
+ expect(result).toContain("Header");
+ expect(result).toContain("Cell");
+ });
+
+ it("returns plain text with line breaks on parse error", () => {
+ expect(parseMarkdown("Normal text\nWith newline")).toBeTruthy();
+ });
+
+ it("handles special characters", () => {
+ const result = parseMarkdown("Text with & and < and >");
+ expect(result).toContain("&");
+ expect(result).toContain("<");
+ expect(result).toContain(">");
+ });
+
+ it("handles URLs in text", () => {
+ const result = parseMarkdown("Visit https://example.com");
+ expect(result).toBeTruthy();
+ });
+
+ it("parses nested markdown", () => {
+ const result = parseMarkdown("**bold with *italic* inside**");
+ expect(result).toContain("");
+ expect(result).toContain("");
+ });
+
+ it("handles multiple paragraphs", () => {
+ const result = parseMarkdown("Paragraph 1\n\nParagraph 2");
+ expect(result).toContain("");
+ expect(result).toContain("Paragraph 1");
+ expect(result).toContain("Paragraph 2");
+ });
+});
diff --git a/src/lib/__tests__/slug.test.ts b/src/lib/slug.test.ts
similarity index 98%
rename from src/lib/__tests__/slug.test.ts
rename to src/lib/slug.test.ts
index 23ba59f..ba22ce1 100644
--- a/src/lib/__tests__/slug.test.ts
+++ b/src/lib/slug.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { generateSlug, isValidSlug, sanitizeSlug } from "../slug";
+import { generateSlug, isValidSlug, sanitizeSlug } from "./slug";
describe("generateSlug", () => {
it("converts basic text to lowercase slug", () => {
diff --git a/src/lib/stageUtils.test.ts b/src/lib/stageUtils.test.ts
new file mode 100644
index 0000000..b16c9dd
--- /dev/null
+++ b/src/lib/stageUtils.test.ts
@@ -0,0 +1,164 @@
+import { describe, expect, it } from "vitest";
+import { sortStagesByOrder } from "./stageUtils";
+
+describe("sortStagesByOrder", () => {
+ it("sorts stages with order > 0 by their order value", () => {
+ const stages = [
+ { name: "Stage C", stage_order: 3 },
+ { name: "Stage A", stage_order: 1 },
+ { name: "Stage B", stage_order: 2 },
+ ];
+
+ const sorted = sortStagesByOrder(stages);
+
+ expect(sorted[0].name).toBe("Stage A");
+ expect(sorted[1].name).toBe("Stage B");
+ expect(sorted[2].name).toBe("Stage C");
+ });
+
+ it("places stages with order 0 at the end, sorted alphabetically", () => {
+ const stages = [
+ { name: "Stage B", stage_order: 0 },
+ { name: "Stage A", stage_order: 0 },
+ { name: "Stage C", stage_order: 0 },
+ ];
+
+ const sorted = sortStagesByOrder(stages);
+
+ expect(sorted[0].name).toBe("Stage A");
+ expect(sorted[1].name).toBe("Stage B");
+ expect(sorted[2].name).toBe("Stage C");
+ });
+
+ it("places ordered stages before unordered stages", () => {
+ const stages = [
+ { name: "Zebra Stage", stage_order: 0 },
+ { name: "Main Stage", stage_order: 1 },
+ { name: "Second Stage", stage_order: 2 },
+ { name: "Alpha Stage", stage_order: 0 },
+ ];
+
+ const sorted = sortStagesByOrder(stages);
+
+ expect(sorted[0].name).toBe("Main Stage");
+ expect(sorted[1].name).toBe("Second Stage");
+ expect(sorted[2].name).toBe("Alpha Stage");
+ expect(sorted[3].name).toBe("Zebra Stage");
+ });
+
+ it("handles null stage_order as 0", () => {
+ const stages: Array<{ name: string; stage_order: number | null }> = [
+ { name: "Stage B", stage_order: null },
+ { name: "Stage A", stage_order: 1 },
+ { name: "Stage C", stage_order: null },
+ ];
+
+ const sorted = sortStagesByOrder(
+ stages as Array<{ name: string; stage_order: number }>,
+ );
+
+ expect(sorted[0].name).toBe("Stage A");
+ expect(sorted[1].name).toBe("Stage B");
+ expect(sorted[2].name).toBe("Stage C");
+ });
+
+ it("handles undefined stage_order as 0", () => {
+ const stages: Array<{ name: string; stage_order: number | undefined }> = [
+ { name: "Stage B", stage_order: undefined },
+ { name: "Stage A", stage_order: 1 },
+ { name: "Stage C", stage_order: undefined },
+ ];
+
+ const sorted = sortStagesByOrder(
+ stages as Array<{ name: string; stage_order: number }>,
+ );
+
+ expect(sorted[0].name).toBe("Stage A");
+ expect(sorted[1].name).toBe("Stage B");
+ expect(sorted[2].name).toBe("Stage C");
+ });
+
+ it("handles mixed ordered and unordered stages", () => {
+ const stages = [
+ { name: "Unordered Z", stage_order: 0 },
+ { name: "Ordered 3", stage_order: 3 },
+ { name: "Unordered A", stage_order: 0 },
+ { name: "Ordered 1", stage_order: 1 },
+ { name: "Ordered 2", stage_order: 2 },
+ { name: "Unordered M", stage_order: 0 },
+ ];
+
+ const sorted = sortStagesByOrder(stages);
+
+ expect(sorted[0].name).toBe("Ordered 1");
+ expect(sorted[1].name).toBe("Ordered 2");
+ expect(sorted[2].name).toBe("Ordered 3");
+ expect(sorted[3].name).toBe("Unordered A");
+ expect(sorted[4].name).toBe("Unordered M");
+ expect(sorted[5].name).toBe("Unordered Z");
+ });
+
+ it("handles empty array", () => {
+ const stages: Array<{ name: string; stage_order: number | null }> = [];
+ const sorted = sortStagesByOrder(
+ stages as Array<{ name: string; stage_order: number }>,
+ );
+ expect(sorted).toEqual([]);
+ });
+
+ it("handles single stage", () => {
+ const stages = [{ name: "Only Stage", stage_order: 1 }];
+ const sorted = sortStagesByOrder(stages);
+ expect(sorted).toHaveLength(1);
+ expect(sorted[0].name).toBe("Only Stage");
+ });
+
+ it("preserves other properties of stages", () => {
+ const stages = [
+ { name: "Stage B", stage_order: 2, color: "#ff0000", id: "b" },
+ { name: "Stage A", stage_order: 1, color: "#00ff00", id: "a" },
+ ];
+
+ const sorted = sortStagesByOrder(stages);
+
+ expect(sorted[0]).toEqual({
+ name: "Stage A",
+ stage_order: 1,
+ color: "#00ff00",
+ id: "a",
+ });
+ expect(sorted[1]).toEqual({
+ name: "Stage B",
+ stage_order: 2,
+ color: "#ff0000",
+ id: "b",
+ });
+ });
+
+ it("is stable for stages with same order", () => {
+ const stages = [
+ { name: "Stage C", stage_order: 1 },
+ { name: "Stage A", stage_order: 1 },
+ { name: "Stage B", stage_order: 1 },
+ ];
+
+ const sorted = sortStagesByOrder(stages);
+
+ expect(sorted).toHaveLength(3);
+ expect(sorted.every((s) => s.stage_order === 1)).toBe(true);
+ });
+
+ it("handles case-sensitive alphabetical sorting", () => {
+ const stages = [
+ { name: "zebra", stage_order: 0 },
+ { name: "Apple", stage_order: 0 },
+ { name: "banana", stage_order: 0 },
+ ];
+
+ const sorted = sortStagesByOrder(stages);
+
+ expect(sorted[0].name).toBe("Apple");
+ expect(sorted[1].name).toBe("banana");
+ expect(sorted[2].name).toBe("zebra");
+ });
+});
diff --git a/src/lib/__tests__/textAlignment.test.ts b/src/lib/textAlignment.test.ts
similarity index 97%
rename from src/lib/__tests__/textAlignment.test.ts
rename to src/lib/textAlignment.test.ts
index 1094931..9d81c27 100644
--- a/src/lib/__tests__/textAlignment.test.ts
+++ b/src/lib/textAlignment.test.ts
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
-import { detectTextAlignment, getTextAlignmentClasses } from "../textAlignment";
+import { detectTextAlignment, getTextAlignmentClasses } from "./textAlignment";
describe("textAlignment", () => {
describe("detectTextAlignment", () => {
diff --git a/src/lib/timeUtils.test.ts b/src/lib/timeUtils.test.ts
new file mode 100644
index 0000000..e715eb4
--- /dev/null
+++ b/src/lib/timeUtils.test.ts
@@ -0,0 +1,295 @@
+import { describe, expect, it } from "vitest";
+import {
+ formatTimeRange,
+ formatDateTime,
+ formatTimeOnly,
+ toDatetimeLocal,
+ toISOString,
+ combineDateAndTime,
+ convertLocalTimeToUTC,
+} from "./timeUtils";
+
+describe("formatTimeRange", () => {
+ it("returns null when both times are null", () => {
+ expect(formatTimeRange(null, null)).toBeNull();
+ });
+
+ it("formats start time only", () => {
+ const result = formatTimeRange("2024-12-15T14:00:00Z", null);
+ expect(result).toContain("Starts:");
+ expect(result).toContain("Dec 15");
+ });
+
+ it("formats end time only", () => {
+ const result = formatTimeRange(null, "2024-12-15T16:00:00Z");
+ expect(result).toContain("Ends:");
+ expect(result).toContain("Dec 15");
+ });
+
+ it("formats time range on same day in 12-hour format", () => {
+ const result = formatTimeRange(
+ "2024-12-15T14:00:00Z",
+ "2024-12-15T16:00:00Z",
+ );
+ expect(result).toBeTruthy();
+ expect(result).toContain("-");
+ });
+
+ it("formats time range on same day in 24-hour format", () => {
+ const result = formatTimeRange(
+ "2024-12-15T14:00:00Z",
+ "2024-12-15T16:00:00Z",
+ true,
+ );
+ expect(result).toBeTruthy();
+ expect(result).toContain("-");
+ expect(result).toMatch(/\d{2}:\d{2}/);
+ });
+
+ it("formats time range across different days", () => {
+ const result = formatTimeRange(
+ "2024-12-15T10:00:00Z",
+ "2024-12-16T14:00:00Z",
+ );
+ expect(result).toContain("Dec 15");
+ expect(result).toContain("Dec 16");
+ });
+
+ it("returns null for invalid start time", () => {
+ const result = formatTimeRange("invalid", "2024-12-15T16:00:00Z");
+ expect(result).toContain("Ends:");
+ });
+
+ it("returns null for invalid end time", () => {
+ const result = formatTimeRange("2024-12-15T14:00:00Z", "invalid");
+ expect(result).toContain("Starts:");
+ });
+
+ it("returns null for both invalid times", () => {
+ expect(formatTimeRange("invalid", "also-invalid")).toBeNull();
+ });
+});
+
+describe("formatDateTime", () => {
+ it("returns null for null input", () => {
+ expect(formatDateTime(null)).toBeNull();
+ });
+
+ it("formats date and time in 12-hour format", () => {
+ const result = formatDateTime("2024-12-15T14:30:00Z");
+ expect(result).toContain("Dec 15");
+ });
+
+ it("formats date and time in 24-hour format", () => {
+ const result = formatDateTime("2024-12-15T14:30:00Z", true);
+ expect(result).toContain("Dec 15");
+ expect(result).toMatch(/\d{2}:\d{2}/);
+ });
+
+ it("returns null for invalid date", () => {
+ expect(formatDateTime("invalid-date")).toBeNull();
+ });
+
+ it("handles different months", () => {
+ const jan = formatDateTime("2024-01-15T14:00:00Z");
+ const jun = formatDateTime("2024-06-15T14:00:00Z");
+ expect(jan).toContain("Jan");
+ expect(jun).toContain("Jun");
+ });
+});
+
+describe("formatTimeOnly", () => {
+ it("returns null for null start time", () => {
+ expect(formatTimeOnly(null, null)).toBeNull();
+ });
+
+ it("formats single time in 12-hour format", () => {
+ const result = formatTimeOnly("2024-12-15T14:30:00Z", null);
+ expect(result).toBeTruthy();
+ });
+
+ it("formats single time in 24-hour format", () => {
+ const result = formatTimeOnly("2024-12-15T14:30:00Z", null, true);
+ expect(result).toMatch(/\d{2}:\d{2}/);
+ });
+
+ it("formats time range in 12-hour format", () => {
+ const result = formatTimeOnly(
+ "2024-12-15T14:00:00Z",
+ "2024-12-15T16:00:00Z",
+ );
+ expect(result).toContain("-");
+ });
+
+ it("formats time range in 24-hour format", () => {
+ const result = formatTimeOnly(
+ "2024-12-15T14:00:00Z",
+ "2024-12-15T16:00:00Z",
+ true,
+ );
+ expect(result).toContain("-");
+ expect(result).toMatch(/\d{2}:\d{2}/);
+ });
+
+ it("returns null for invalid start time", () => {
+ expect(formatTimeOnly("invalid", null)).toBeNull();
+ });
+
+ it("handles invalid end time gracefully", () => {
+ const result = formatTimeOnly("2024-12-15T14:00:00Z", "invalid");
+ expect(result).toBeTruthy();
+ expect(result).not.toContain("-");
+ });
+});
+
+describe("toDatetimeLocal", () => {
+ it("returns empty string for null input", () => {
+ expect(toDatetimeLocal(null)).toBe("");
+ });
+
+ it("returns empty string for empty string input", () => {
+ expect(toDatetimeLocal("")).toBe("");
+ });
+
+ it("converts ISO string to datetime-local format", () => {
+ const result = toDatetimeLocal("2024-12-15T14:30:00Z");
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/);
+ });
+
+ it("handles different times", () => {
+ const morning = toDatetimeLocal("2024-12-15T08:00:00Z");
+ const evening = toDatetimeLocal("2024-12-15T20:00:00Z");
+ expect(morning).toBeTruthy();
+ expect(evening).toBeTruthy();
+ });
+});
+
+describe("toISOString", () => {
+ it("returns empty string for empty input", () => {
+ expect(toISOString("")).toBe("");
+ });
+
+ it("converts datetime-local format to ISO string", () => {
+ const result = toISOString("2024-12-15T14:30");
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
+ });
+
+ it("handles different datetime values", () => {
+ const result1 = toISOString("2024-01-01T00:00");
+ const result2 = toISOString("2024-12-31T23:59");
+ expect(result1).toBeTruthy();
+ expect(result2).toBeTruthy();
+ });
+});
+
+describe("combineDateAndTime", () => {
+ it("returns null when date is undefined", () => {
+ expect(combineDateAndTime(undefined, "14:30")).toBeNull();
+ });
+
+ it("returns null when time is undefined", () => {
+ expect(combineDateAndTime("2024-12-15", undefined)).toBeNull();
+ });
+
+ it("returns null when both are undefined", () => {
+ expect(combineDateAndTime(undefined, undefined)).toBeNull();
+ });
+
+ it("combines date and time strings", () => {
+ const result = combineDateAndTime("2024-12-15", "14:30");
+ expect(result).toBe("2024-12-15 14:30:00");
+ });
+
+ it("pads single digit hours", () => {
+ const result = combineDateAndTime("2024-12-15", "8:30");
+ expect(result).toBe("2024-12-15 08:30:00");
+ });
+
+ it("handles time with seconds", () => {
+ const result = combineDateAndTime("2024-12-15", "14:30:45");
+ expect(result).toBe("2024-12-15 14:30:45");
+ });
+
+ it("pads time without seconds", () => {
+ const result = combineDateAndTime("2024-12-15", "14:30");
+ expect(result).toBe("2024-12-15 14:30:00");
+ });
+
+ it("trims whitespace from inputs", () => {
+ const result = combineDateAndTime(" 2024-12-15 ", " 14:30 ");
+ expect(result).toBe("2024-12-15 14:30:00");
+ });
+
+ it("handles various time formats", () => {
+ expect(combineDateAndTime("2024-12-15", "8:00")).toBe(
+ "2024-12-15 08:00:00",
+ );
+ expect(combineDateAndTime("2024-12-15", "08:00")).toBe(
+ "2024-12-15 08:00:00",
+ );
+ expect(combineDateAndTime("2024-12-15", "8:00:00")).toBe(
+ "2024-12-15 08:00:00",
+ );
+ expect(combineDateAndTime("2024-12-15", "08:00:00")).toBe(
+ "2024-12-15 08:00:00",
+ );
+ });
+});
+
+describe("convertLocalTimeToUTC", () => {
+ it("returns null for undefined input", () => {
+ expect(convertLocalTimeToUTC(undefined, "America/New_York")).toBeNull();
+ });
+
+ it("returns null for empty string", () => {
+ expect(convertLocalTimeToUTC("", "America/New_York")).toBeNull();
+ });
+
+ it("converts local time to UTC", () => {
+ const result = convertLocalTimeToUTC(
+ "2024-12-15 14:30:00",
+ "America/New_York",
+ );
+ expect(result).toBeTruthy();
+ if (result) {
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
+ }
+ });
+
+ it("handles different timezones", () => {
+ const resultNY = convertLocalTimeToUTC(
+ "2024-12-15 14:30:00",
+ "America/New_York",
+ );
+ const resultLA = convertLocalTimeToUTC(
+ "2024-12-15 14:30:00",
+ "America/Los_Angeles",
+ );
+ expect(resultNY).toBeTruthy();
+ expect(resultLA).toBeTruthy();
+ expect(resultNY).not.toBe(resultLA);
+ });
+
+ it("handles UTC timezone", () => {
+ const result = convertLocalTimeToUTC("2024-12-15 14:30:00", "UTC");
+ expect(result).toBeTruthy();
+ });
+
+ it("returns null for invalid date string", () => {
+ const result = convertLocalTimeToUTC("invalid-date", "America/New_York");
+ expect(result).toBeNull();
+ });
+
+ it("handles different date formats", () => {
+ const result1 = convertLocalTimeToUTC(
+ "2024-12-15T14:30:00",
+ "America/New_York",
+ );
+ const result2 = convertLocalTimeToUTC(
+ "2024-12-15 14:30:00",
+ "America/New_York",
+ );
+ expect(result1).toBeTruthy();
+ expect(result2).toBeTruthy();
+ });
+});
diff --git a/src/lib/voteConfig.test.ts b/src/lib/voteConfig.test.ts
new file mode 100644
index 0000000..72f6661
--- /dev/null
+++ b/src/lib/voteConfig.test.ts
@@ -0,0 +1,167 @@
+import { describe, expect, it } from "vitest";
+import {
+ VOTE_CONFIG,
+ VOTES_TYPES,
+ getVoteConfig,
+ getVoteValue,
+ type VoteType,
+} from "./voteConfig";
+import { Star, Heart, X } from "lucide-react";
+
+describe("VOTE_CONFIG", () => {
+ it("has correct structure for mustGo", () => {
+ expect(VOTE_CONFIG.mustGo).toBeDefined();
+ expect(VOTE_CONFIG.mustGo.value).toBe(2);
+ expect(VOTE_CONFIG.mustGo.label).toBe("Must Go");
+ expect(VOTE_CONFIG.mustGo.icon).toBe(Star);
+ });
+
+ it("has correct structure for interested", () => {
+ expect(VOTE_CONFIG.interested).toBeDefined();
+ expect(VOTE_CONFIG.interested.value).toBe(1);
+ expect(VOTE_CONFIG.interested.label).toBe("Interested");
+ expect(VOTE_CONFIG.interested.icon).toBe(Heart);
+ });
+
+ it("has correct structure for wontGo", () => {
+ expect(VOTE_CONFIG.wontGo).toBeDefined();
+ expect(VOTE_CONFIG.wontGo.value).toBe(-1);
+ expect(VOTE_CONFIG.wontGo.label).toBe("Won't Go");
+ expect(VOTE_CONFIG.wontGo.icon).toBe(X);
+ });
+
+ it("has consistent properties across all vote types", () => {
+ const requiredProps = [
+ "value",
+ "label",
+ "icon",
+ "bgColor",
+ "iconColor",
+ "textColor",
+ "descColor",
+ "circleColor",
+ "buttonSelected",
+ "buttonUnselected",
+ "spinnerColor",
+ "description",
+ ];
+
+ VOTES_TYPES.forEach((voteType) => {
+ requiredProps.forEach((prop) => {
+ expect(VOTE_CONFIG[voteType]).toHaveProperty(prop);
+ });
+ });
+ });
+
+ it("has unique values for each vote type", () => {
+ const values = VOTES_TYPES.map((type) => VOTE_CONFIG[type].value);
+ const uniqueValues = new Set(values);
+ expect(uniqueValues.size).toBe(VOTES_TYPES.length);
+ });
+
+ it("has valid color classes", () => {
+ VOTES_TYPES.forEach((voteType) => {
+ const config = VOTE_CONFIG[voteType];
+ expect(config.bgColor).toMatch(/^bg-/);
+ expect(config.iconColor).toMatch(/^text-/);
+ expect(config.textColor).toMatch(/^text-/);
+ expect(config.circleColor).toMatch(/^bg-/);
+ });
+ });
+});
+
+describe("VOTES_TYPES", () => {
+ it("contains all vote types", () => {
+ expect(VOTES_TYPES).toEqual(["mustGo", "interested", "wontGo"]);
+ });
+
+ it("is a readonly array", () => {
+ expect(VOTES_TYPES).toHaveLength(3);
+ });
+});
+
+describe("getVoteConfig", () => {
+ it("returns correct vote type for value 2", () => {
+ expect(getVoteConfig(2)).toBe("mustGo");
+ });
+
+ it("returns correct vote type for value 1", () => {
+ expect(getVoteConfig(1)).toBe("interested");
+ });
+
+ it("returns correct vote type for value -1", () => {
+ expect(getVoteConfig(-1)).toBe("wontGo");
+ });
+
+ it("returns undefined for invalid values", () => {
+ expect(getVoteConfig(0)).toBeUndefined();
+ expect(getVoteConfig(3)).toBeUndefined();
+ expect(getVoteConfig(-2)).toBeUndefined();
+ expect(getVoteConfig(999)).toBeUndefined();
+ });
+
+ it("returns undefined for non-numeric values", () => {
+ expect(getVoteConfig(NaN)).toBeUndefined();
+ expect(getVoteConfig(Infinity)).toBeUndefined();
+ expect(getVoteConfig(-Infinity)).toBeUndefined();
+ });
+
+ it("returns correct vote type for all valid values", () => {
+ const validMappings: Array<[number, VoteType]> = [
+ [2, "mustGo"],
+ [1, "interested"],
+ [-1, "wontGo"],
+ ];
+
+ validMappings.forEach(([value, expectedType]) => {
+ expect(getVoteConfig(value)).toBe(expectedType);
+ });
+ });
+});
+
+describe("getVoteValue", () => {
+ it("returns 2 for mustGo", () => {
+ expect(getVoteValue("mustGo")).toBe(2);
+ });
+
+ it("returns 1 for interested", () => {
+ expect(getVoteValue("interested")).toBe(1);
+ });
+
+ it("returns -1 for wontGo", () => {
+ expect(getVoteValue("wontGo")).toBe(-1);
+ });
+
+ it("returns correct values for all vote types", () => {
+ const expectedValues: Record = {
+ mustGo: 2,
+ interested: 1,
+ wontGo: -1,
+ };
+
+ VOTES_TYPES.forEach((voteType) => {
+ expect(getVoteValue(voteType)).toBe(expectedValues[voteType]);
+ });
+ });
+});
+
+describe("getVoteConfig and getVoteValue integration", () => {
+ it("should be inverse operations for valid values", () => {
+ const validValues = [2, 1, -1];
+
+ validValues.forEach((value) => {
+ const voteType = getVoteConfig(value);
+ expect(voteType).toBeDefined();
+ if (voteType) {
+ expect(getVoteValue(voteType)).toBe(value);
+ }
+ });
+ });
+
+ it("should be inverse operations for valid types", () => {
+ VOTES_TYPES.forEach((voteType) => {
+ const value = getVoteValue(voteType);
+ expect(getVoteConfig(value)).toBe(voteType);
+ });
+ });
+});
diff --git a/src/test/setup.ts b/src/test/setup.ts
new file mode 100644
index 0000000..0c6b74b
--- /dev/null
+++ b/src/test/setup.ts
@@ -0,0 +1,35 @@
+import "@testing-library/jest-dom/vitest";
+
+// Polyfill for ArrayBuffer.prototype.resizable and SharedArrayBuffer.prototype.growable
+// These are needed by webidl-conversions package
+if (typeof ArrayBuffer !== "undefined" && !Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "resizable")) {
+ Object.defineProperty(ArrayBuffer.prototype, "resizable", {
+ get() {
+ return false;
+ },
+ configurable: true,
+ });
+}
+
+if (typeof SharedArrayBuffer !== "undefined" && !Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, "growable")) {
+ Object.defineProperty(SharedArrayBuffer.prototype, "growable", {
+ get() {
+ return false;
+ },
+ configurable: true,
+ });
+}
+
+// Polyfill for webidl-conversions and whatwg-url
+if (typeof global.Set === "undefined") {
+ global.Set = Set;
+}
+if (typeof global.Map === "undefined") {
+ global.Map = Map;
+}
+if (typeof global.WeakMap === "undefined") {
+ global.WeakMap = WeakMap;
+}
+if (typeof global.WeakSet === "undefined") {
+ global.WeakSet = WeakSet;
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index e11a5ee..ddaf447 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -7,8 +7,10 @@ interface ViteTypeOptions {
}
interface ImportMetaEnv {
- readonly SUPABASE_URL: string;
- readonly SUPABASE_PUBLISHABLE_KEY: string;
+ readonly VITE_SUPABASE_URL: string;
+ readonly VITE_SUPABASE_PUBLISHABLE_KEY: string;
+ readonly VITE_PUBLIC_POSTHOG_KEY: string;
+ readonly VITE_PUBLIC_POSTHOG_HOST: string;
// more env variables...
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 3133162..a826964 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -4,19 +4,17 @@
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
-
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
-
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true
},
- "include": ["vite.config.ts"]
+ "include": ["vite.config.ts", "vitest.config.ts"]
}
diff --git a/vite.config.test.ts b/vitest.config.ts
similarity index 85%
rename from vite.config.test.ts
rename to vitest.config.ts
index 21908a4..e8ed08e 100644
--- a/vite.config.test.ts
+++ b/vitest.config.ts
@@ -7,6 +7,9 @@ export default defineConfig({
test: {
globals: true,
environment: "jsdom",
+ globalSetup: "./vitest.global-setup.ts",
+ setupFiles: ["./src/test/setup.ts"],
+ pool: "forks",
exclude: [
"**/node_modules/**",
"**/dist/**",
diff --git a/vitest.global-setup.ts b/vitest.global-setup.ts
new file mode 100644
index 0000000..7d4774a
--- /dev/null
+++ b/vitest.global-setup.ts
@@ -0,0 +1,21 @@
+export default function setup() {
+ // Polyfill for ArrayBuffer.prototype.resizable and SharedArrayBuffer.prototype.growable
+ // These are needed by webidl-conversions package which is loaded before test setup files
+ if (typeof ArrayBuffer !== "undefined" && !Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "resizable")) {
+ Object.defineProperty(ArrayBuffer.prototype, "resizable", {
+ get() {
+ return false;
+ },
+ configurable: true,
+ });
+ }
+
+ if (typeof SharedArrayBuffer !== "undefined" && !Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, "growable")) {
+ Object.defineProperty(SharedArrayBuffer.prototype, "growable", {
+ get() {
+ return false;
+ },
+ configurable: true,
+ });
+ }
+}