eletrotupi / tcc/ commit / e11faeb

frontend: cover the two ephemeral stores

The mood-entry component list and the care-action link hand-off had no
offline check, so a lost default intensity, a broken de-dupe or a link
that outlives its thirty-minute window could only be caught by wiring
the screens together on a device. Both stores are now driven through
their public actions, with the expiry boundary pinned
Pedro Lucas Porcellis porcellis@eletrotupi.com · 9 days ago e11faebd227a1ab47246f3cf6d02d42cc42a6c8c Browse files
Parents b3ead45
Changed files
2
Additions
+204
Deletions
0
/dev/null → frontend/tests/stores/careActionLink.unit.test.ts
1 +
import { useCareActionLinkStore } from "@/stores";
2 +

3 +
const TTL_MS = 30 * 60 * 1000;
4 +
const START = new Date(2025, 0, 15, 10, 0, 0);
5 +

6 +
const CLEARED = {
7 +
  pendingId: null,
8 +
  expiresAt: null,
9 +
  triggerLinked: false,
10 +
  moodLinked: false,
11 +
};
12 +

13 +
const SIDES = [
14 +
  {
15 +
    side: "trigger",
16 +
    link: "linkTrigger",
17 +
    otherLink: "linkMood",
18 +
    linkedFlag: "triggerLinked",
19 +
  },
20 +
  {
21 +
    side: "mood",
22 +
    link: "linkMood",
23 +
    otherLink: "linkTrigger",
24 +
    linkedFlag: "moodLinked",
25 +
  },
26 +
] as const;
27 +

28 +
const state = () => useCareActionLinkStore.getState();
29 +

30 +
beforeEach(() => {
31 +
  jest.useFakeTimers();
32 +
  jest.setSystemTime(START);
33 +
  useCareActionLinkStore.setState(CLEARED);
34 +
});
35 +

36 +
afterEach(() => {
37 +
  jest.useRealTimers();
38 +
});
39 +

40 +
describe("setPending", () => {
41 +
  it("arms the expiry thirty minutes out and drops the linked sides", () => {
42 +
    useCareActionLinkStore.setState({ triggerLinked: true, moodLinked: true });
43 +

44 +
    state().setPending(42);
45 +

46 +
    expect(state().pendingId).toBe(42);
47 +
    expect(state().expiresAt).toBe(START.getTime() + TTL_MS);
48 +
    expect(state().triggerLinked).toBe(false);
49 +
    expect(state().moodLinked).toBe(false);
50 +
  });
51 +
});
52 +

53 +
describe.each(SIDES)(
54 +
  "linking the $side side",
55 +
  ({ link, otherLink, linkedFlag }) => {
56 +
    it("arms that side and hands back the pending id", () => {
57 +
      state().setPending(7);
58 +

59 +
      expect(state()[link]()).toBe(7);
60 +
      expect(state().pendingId).toBe(7);
61 +
      expect(state()[linkedFlag]).toBe(true);
62 +
    });
63 +

64 +
    it("clears the pending link once the other side links", () => {
65 +
      state().setPending(7);
66 +
      state()[link]();
67 +

68 +
      expect(state()[otherLink]()).toBe(7);
69 +
      expect(state()).toMatchObject(CLEARED);
70 +
    });
71 +

72 +
    it("refuses a repeat of its own side", () => {
73 +
      state().setPending(7);
74 +
      state()[link]();
75 +

76 +
      expect(state()[link]()).toBeNull();
77 +
      expect(state().pendingId).toBe(7);
78 +
      expect(state()[linkedFlag]).toBe(true);
79 +
    });
80 +

81 +
    it("still hands back the id at the exact expiry boundary", () => {
82 +
      state().setPending(9);
83 +
      jest.setSystemTime(START.getTime() + TTL_MS);
84 +

85 +
      expect(state()[link]()).toBe(9);
86 +
      expect(state()[linkedFlag]).toBe(true);
87 +
    });
88 +

89 +
    it("discards an expired pending action instead of handing it off", () => {
90 +
      state().setPending(9);
91 +
      jest.setSystemTime(START.getTime() + TTL_MS + 1);
92 +

93 +
      expect(state()[link]()).toBeNull();
94 +
      expect(state()).toMatchObject(CLEARED);
95 +
    });
96 +
  },
97 +
);
98 +

99 +
describe("with nothing pending", () => {
100 +
  it("hands back null for either side", () => {
101 +
    expect(state().linkTrigger()).toBeNull();
102 +
    expect(state().linkMood()).toBeNull();
103 +
  });
104 +
});
105 +

106 +
describe("clear", () => {
107 +
  it("resets every field", () => {
108 +
    state().setPending(3);
109 +
    useCareActionLinkStore.setState({ triggerLinked: true, moodLinked: true });
110 +

111 +
    state().clear();
112 +

113 +
    expect(state()).toMatchObject(CLEARED);
114 +
  });
115 +
});
/dev/null → frontend/tests/stores/moodEntry.unit.test.ts
1 +
import { useMoodEntryStore } from "@/stores";
2 +

3 +
const state = () => useMoodEntryStore.getState();
4 +
const component = (id: string, intensity: number) => ({ id, intensity });
5 +

6 +
beforeEach(() => {
7 +
  useMoodEntryStore.setState({ selectedMood: null, components: [] });
8 +
});
9 +

10 +
describe("setSelectedMood", () => {
11 +
  it("records the selected mood", () => {
12 +
    state().setSelectedMood("good");
13 +

14 +
    expect(state().selectedMood).toBe("good");
15 +
  });
16 +
});
17 +

18 +
describe("setComponents", () => {
19 +
  it("replaces the whole component list at once", () => {
20 +
    state().setComponents([component("joy", 3), component("calm", 2)]);
21 +

22 +
    expect(state().components).toStrictEqual([
23 +
      component("joy", 3),
24 +
      component("calm", 2),
25 +
    ]);
26 +
  });
27 +
});
28 +

29 +
describe("addComponent", () => {
30 +
  it("appends with the default intensity", () => {
31 +
    state().addComponent("joy");
32 +

33 +
    expect(state().components).toStrictEqual([component("joy", 1)]);
34 +
  });
35 +

36 +
  it("keeps the order the components were added in", () => {
37 +
    state().addComponent("joy");
38 +
    state().addComponent("calm");
39 +

40 +
    expect(state().components).toStrictEqual([
41 +
      component("joy", 1),
42 +
      component("calm", 1),
43 +
    ]);
44 +
  });
45 +

46 +
  it("ignores a re-add, keeping the intensity already set", () => {
47 +
    state().addComponent("joy");
48 +
    state().setComponentIntensity("joy", 3);
49 +

50 +
    state().addComponent("joy");
51 +

52 +
    expect(state().components).toStrictEqual([component("joy", 3)]);
53 +
  });
54 +
});
55 +

56 +
describe("removeComponent", () => {
57 +
  it("drops the component and leaves the rest", () => {
58 +
    state().setComponents([component("joy", 1), component("calm", 2)]);
59 +

60 +
    state().removeComponent("joy");
61 +

62 +
    expect(state().components).toStrictEqual([component("calm", 2)]);
63 +
  });
64 +
});
65 +

66 +
describe("setComponentIntensity", () => {
67 +
  it("changes only the target component", () => {
68 +
    state().setComponents([component("joy", 2), component("calm", 1)]);
69 +

70 +
    state().setComponentIntensity("calm", 3);
71 +

72 +
    expect(state().components).toStrictEqual([
73 +
      component("joy", 2),
74 +
      component("calm", 3),
75 +
    ]);
76 +
  });
77 +
});
78 +

79 +
describe("reset", () => {
80 +
  it("clears the mood and the component list", () => {
81 +
    state().setSelectedMood("good");
82 +
    state().addComponent("joy");
83 +

84 +
    state().reset();
85 +

86 +
    expect(state().selectedMood).toBeNull();
87 +
    expect(state().components).toStrictEqual([]);
88 +
  });
89 +
});