@primitiv-ui/react 0.1.36 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitiv-ui/react",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Headless, accessible React components built on the WAI-ARIA authoring patterns. Zero styles ship with this package — bring your own (CSS, Tailwind, CSS-in-JS, design tokens).",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -36,8 +36,7 @@ describe("Tooltip.Provider — skip-delay coordination", () => {
36
36
  expect(screen.getByText("Second tip")).toBeInTheDocument();
37
37
  });
38
38
 
39
- it("clears a pending skip-delay timer when a tooltip opens again within the skip window", async () => {
40
- const user = userEvent.setup();
39
+ it("clears a pending skip-delay timer when a tooltip opens again within the skip window", () => {
41
40
  const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
42
41
  const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
43
42
 
@@ -51,19 +50,36 @@ describe("Tooltip.Provider — skip-delay coordination", () => {
51
50
  </Tooltip.Provider>,
52
51
  );
53
52
 
54
- await user.tab();
53
+ // Driven with `fireEvent`, not `userEvent`: `delayDuration={0}` makes open and
54
+ // close synchronous, so focus/blur schedule and clear the skip-delay timer with
55
+ // no timers to advance — and, crucially, with no dependence on how a higher-level
56
+ // helper synthesises events. An earlier version tabbed with `userEvent` and then
57
+ // pinned *the first* skip-delay timer by array position; under CI load a second
58
+ // close could schedule another before the reopen, so the handle it cleared was
59
+ // not the one pinned, and the file went flaky.
60
+ const trigger = screen.getByRole("button", { name: "Hover me" });
61
+ fireEvent.focus(trigger); // open
55
62
  expect(screen.getByRole("tooltip")).toBeInTheDocument();
56
- await user.tab();
63
+ fireEvent.blur(trigger); // close → schedules the skip-delay timer
57
64
  expect(screen.queryByRole("tooltip")).toBeNull();
58
65
 
59
- const skipTimerCallIndex = setTimeoutSpy.mock.calls.findIndex(
60
- ([, delay]) => delay === SKIP_DELAY,
61
- );
62
- const timerId = setTimeoutSpy.mock.results[skipTimerCallIndex].value;
63
-
64
- await user.hover(screen.getByRole("button", { name: "Hover me" }));
65
-
66
- expect(clearTimeoutSpy).toHaveBeenCalledWith(timerId);
66
+ const skipTimers = setTimeoutSpy.mock.calls
67
+ .map((call, i) => ({ delay: call[1], id: setTimeoutSpy.mock.results[i].value }))
68
+ .filter(({ delay }) => delay === SKIP_DELAY)
69
+ .map(({ id }) => id);
70
+ expect(skipTimers.length).toBeGreaterThanOrEqual(1);
71
+
72
+ fireEvent.focus(trigger); // reopen within the skip window
73
+
74
+ // Opening again must cancel the pending skip-delay timer. Asserting that *every*
75
+ // skip-delay timer scheduled is now cleared — rather than pinning one handle by
76
+ // position — holds however many close events fired: each earlier timer was
77
+ // cancelled as its replacement was scheduled, and `onOpenGlobally` cancels the
78
+ // last. The never-clear-on-open variant leaves that last timer pending, which
79
+ // fails this.
80
+ for (const timer of skipTimers) {
81
+ expect(clearTimeoutSpy).toHaveBeenCalledWith(timer);
82
+ }
67
83
 
68
84
  clearTimeoutSpy.mockRestore();
69
85
  setTimeoutSpy.mockRestore();