eletrotupi / tcc/ commit / 27bca51

frontend: cover the time helpers and the auth routing decision

The time badges, the today/yesterday formatting and the date-only parse
had no offline check, so an off-by-one at a window edge or a timezone
shift on a date-only value could only surface on a device. The auth
status and the route it picks are now pinned for every status, including
the loading case that deliberately stays on the spinner instead of
navigating
Pedro Lucas Porcellis porcellis@eletrotupi.com · 9 days ago 27bca510c14b3745f6fa52ae54fac66653498781 Browse files
Parents c377fb9
Changed files
2
Additions
+149
Deletions
0
/dev/null → frontend/tests/context/authRouting.unit.test.ts
1 +
import { deriveStatus, routeForStatus } from "@/context/authRouting";
2 +
import type { User } from "@/lib/api";
3 +

4 +
function user(overrides: Partial<User> = {}): User {
5 +
  return {
6 +
    id: 1,
7 +
    email: "ana@example.com",
8 +
    firstName: "Ana",
9 +
    updatedAt: new Date(2025, 0, 15),
10 +
    active: true,
11 +
    ...overrides,
12 +
  };
13 +
}
14 +

15 +
describe("deriveStatus", () => {
16 +
  it("stays loading while the session resolves, even with a user in hand", () => {
17 +
    expect(deriveStatus(true, null)).toBe("loading");
18 +
    expect(deriveStatus(true, user())).toBe("loading");
19 +
  });
20 +

21 +
  it("is signed out without a user", () => {
22 +
    expect(deriveStatus(false, null)).toBe("signedOut");
23 +
  });
24 +

25 +
  it("awaits activation for an account that is not active yet", () => {
26 +
    expect(deriveStatus(false, user({ active: false }))).toBe(
27 +
      "awaitingActivation",
28 +
    );
29 +
  });
30 +

31 +
  it("is active for an active account", () => {
32 +
    expect(deriveStatus(false, user({ active: true }))).toBe("active");
33 +
  });
34 +
});
35 +

36 +
describe("routeForStatus", () => {
37 +
  it("stays put while loading, whatever the user shape", () => {
38 +
    expect(routeForStatus("loading", null)).toBeNull();
39 +
    expect(routeForStatus("loading", user())).toBeNull();
40 +
  });
41 +

42 +
  it("sends a signed-out account to login", () => {
43 +
    expect(routeForStatus("signedOut", null)).toBe("/auth/login");
44 +
  });
45 +

46 +
  it("sends an unactivated account to activation", () => {
47 +
    expect(routeForStatus("awaitingActivation", user({ active: false }))).toBe(
48 +
      "/auth/activate",
49 +
    );
50 +
  });
51 +

52 +
  it("sends an active account that finished onboarding to the feed", () => {
53 +
    expect(
54 +
      routeForStatus("active", user({ onboardingSeenAt: new Date(2025, 0, 15) })),
55 +
    ).toBe("/(tabs)/new");
56 +
  });
57 +

58 +
  it("sends an active account that never finished onboarding to onboarding", () => {
59 +
    expect(routeForStatus("active", user({ onboardingSeenAt: null }))).toBe(
60 +
      "/onboarding",
61 +
    );
62 +
    expect(routeForStatus("active", user())).toBe("/onboarding");
63 +
  });
64 +
});
/dev/null → frontend/tests/utils/time.unit.test.ts
1 +
import {
2 +
  formatDate,
3 +
  formatMoment,
4 +
  getTimeBadge,
5 +
  parseDateOnly,
6 +
} from "@/lib/utils/time";
7 +

8 +
const at = (hour: number, minute = 0) => new Date(2025, 0, 15, hour, minute);
9 +

10 +
beforeEach(() => {
11 +
  jest.useFakeTimers();
12 +
  jest.setSystemTime(new Date(2025, 0, 15, 10, 0, 0));
13 +
});
14 +

15 +
afterEach(() => {
16 +
  jest.useRealTimers();
17 +
});
18 +

19 +
describe("getTimeBadge", () => {
20 +
  it.each([
21 +
    { clock: "00:00", date: at(0), expected: { label: "Madrugada", variant: "purple" } },
22 +
    { clock: "03:30", date: at(3, 30), expected: { label: "Madrugada", variant: "purple" } },
23 +
    { clock: "05:59", date: at(5, 59), expected: { label: "Madrugada", variant: "purple" } },
24 +
    { clock: "06:00", date: at(6), expected: { label: "Manhã", variant: "green" } },
25 +
    { clock: "11:59", date: at(11, 59), expected: { label: "Manhã", variant: "green" } },
26 +
    { clock: "12:00", date: at(12), expected: { label: "Tarde", variant: "orange" } },
27 +
    { clock: "17:59", date: at(17, 59), expected: { label: "Tarde", variant: "orange" } },
28 +
    { clock: "18:00", date: at(18), expected: { label: "Noite", variant: "blue" } },
29 +
    { clock: "23:59", date: at(23, 59), expected: { label: "Noite", variant: "blue" } },
30 +
  ])("badges $clock as $expected.label", ({ date, expected }) => {
31 +
    expect(getTimeBadge(date)).toStrictEqual(expected);
32 +
  });
33 +
});
34 +

35 +
describe("formatMoment", () => {
36 +
  it("labels a moment from earlier today", () => {
37 +
    expect(formatMoment(new Date(2025, 0, 15, 8, 30))).toBe("Hoje às 08h30");
38 +
  });
39 +

40 +
  it("labels today's first minute as today", () => {
41 +
    expect(formatMoment(new Date(2025, 0, 15, 0, 0))).toBe("Hoje às 00h00");
42 +
  });
43 +

44 +
  it("labels last night's final minute as yesterday", () => {
45 +
    expect(formatMoment(new Date(2025, 0, 14, 23, 59))).toBe("Ontem às 23h59");
46 +
  });
47 +

48 +
  it("labels an older moment with the day and month", () => {
49 +
    expect(formatMoment(new Date(2025, 0, 10, 8, 30))).toBe(
50 +
      "10 de jan às 08h30",
51 +
    );
52 +
  });
53 +
});
54 +

55 +
describe("formatDate", () => {
56 +
  it("names today with a short date", () => {
57 +
    expect(formatDate(new Date(2025, 0, 15))).toBe("Hoje, 15/01");
58 +
  });
59 +

60 +
  it("names yesterday with a short date", () => {
61 +
    expect(formatDate(new Date(2025, 0, 14, 23, 59))).toBe("Ontem, 14/01");
62 +
  });
63 +

64 +
  it("names an older date with the year", () => {
65 +
    expect(formatDate(new Date(2025, 0, 10))).toBe("10/01/2025");
66 +
  });
67 +
});
68 +

69 +
describe("parseDateOnly", () => {
70 +
  it("anchors a date-only string inside the day it names", () => {
71 +
    const parsed = parseDateOnly("2025-01-10T00:00:00.000Z");
72 +

73 +
    expect(parsed.toDateString()).toBe(new Date(2025, 0, 10).toDateString());
74 +
  });
75 +

76 +
  it("anchors at local noon so an offset cannot roll the day over", () => {
77 +
    expect(parseDateOnly("2025-01-10T00:00:00.000Z").getHours()).toBe(12);
78 +
  });
79 +

80 +
  it("accepts a Date", () => {
81 +
    const parsed = parseDateOnly(new Date(Date.UTC(2025, 0, 10)));
82 +

83 +
    expect(parsed.toDateString()).toBe(new Date(2025, 0, 10).toDateString());
84 +
  });
85 +
});