Skip to content

Commit dc56bd9

Browse files
Copilothotlong
andcommitted
Add ActionContext tests for ActionProvider, useAction, and useHasActionProvider
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 1a636c1 commit dc56bd9

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/**
2+
* Tests for ActionContext — ActionProvider, useAction, useHasActionProvider
3+
*/
4+
5+
import { describe, it, expect, vi } from 'vitest';
6+
import { renderHook, waitFor, act } from '@testing-library/react';
7+
import React from 'react';
8+
import { ActionProvider, useAction, useHasActionProvider } from '../ActionContext';
9+
10+
describe('ActionContext', () => {
11+
describe('useAction without ActionProvider', () => {
12+
it('falls back to local ActionRunner with default state', () => {
13+
const { result } = renderHook(() => useAction());
14+
15+
expect(result.current.loading).toBe(false);
16+
expect(result.current.error).toBeNull();
17+
expect(result.current.result).toBeNull();
18+
expect(typeof result.current.execute).toBe('function');
19+
expect(typeof result.current.executeChain).toBe('function');
20+
expect(typeof result.current.updateContext).toBe('function');
21+
expect(result.current.runner).toBeDefined();
22+
});
23+
});
24+
25+
describe('useHasActionProvider', () => {
26+
it('returns false without provider', () => {
27+
const { result } = renderHook(() => useHasActionProvider());
28+
29+
expect(result.current).toBe(false);
30+
});
31+
32+
it('returns true with provider', () => {
33+
const wrapper = ({ children }: { children: React.ReactNode }) =>
34+
React.createElement(ActionProvider, { context: {} }, children);
35+
36+
const { result } = renderHook(() => useHasActionProvider(), { wrapper });
37+
38+
expect(result.current).toBe(true);
39+
});
40+
});
41+
42+
describe('ActionProvider basic', () => {
43+
it('children can access execute function', () => {
44+
const wrapper = ({ children }: { children: React.ReactNode }) =>
45+
React.createElement(ActionProvider, { context: {} }, children);
46+
47+
const { result } = renderHook(() => useAction(), { wrapper });
48+
49+
expect(typeof result.current.execute).toBe('function');
50+
expect(typeof result.current.executeChain).toBe('function');
51+
expect(typeof result.current.updateContext).toBe('function');
52+
expect(result.current.loading).toBe(false);
53+
expect(result.current.error).toBeNull();
54+
expect(result.current.result).toBeNull();
55+
});
56+
});
57+
58+
describe('ActionProvider execute', () => {
59+
it('executes a toast action and returns result', async () => {
60+
const wrapper = ({ children }: { children: React.ReactNode }) =>
61+
React.createElement(ActionProvider, { context: {} }, children);
62+
63+
const { result } = renderHook(() => useAction(), { wrapper });
64+
65+
let actionResult: any;
66+
await act(async () => {
67+
actionResult = await result.current.execute({
68+
type: 'script',
69+
execute: 'true',
70+
toast: { showOnSuccess: true },
71+
successMessage: 'Done!',
72+
});
73+
});
74+
75+
expect(actionResult.success).toBe(true);
76+
77+
await waitFor(() => {
78+
expect(result.current.loading).toBe(false);
79+
});
80+
81+
expect(result.current.result).toEqual(actionResult);
82+
expect(result.current.error).toBeNull();
83+
});
84+
});
85+
86+
describe('ActionProvider execute error', () => {
87+
it('executes an action that fails and sets error state', async () => {
88+
const wrapper = ({ children }: { children: React.ReactNode }) =>
89+
React.createElement(ActionProvider, { context: {} }, children);
90+
91+
const { result } = renderHook(() => useAction(), { wrapper });
92+
93+
let actionResult: any;
94+
await act(async () => {
95+
actionResult = await result.current.execute({
96+
onClick: vi.fn().mockRejectedValue(new Error('boom')),
97+
});
98+
});
99+
100+
await waitFor(() => {
101+
expect(result.current.loading).toBe(false);
102+
});
103+
104+
expect(actionResult.success).toBe(false);
105+
expect(result.current.error).toBeTruthy();
106+
expect(result.current.result?.success).toBe(false);
107+
});
108+
});
109+
110+
describe('ActionProvider executeChain', () => {
111+
it('executes a chain of actions', async () => {
112+
const wrapper = ({ children }: { children: React.ReactNode }) =>
113+
React.createElement(ActionProvider, { context: {} }, children);
114+
115+
const { result } = renderHook(() => useAction(), { wrapper });
116+
117+
let chainResult: any;
118+
await act(async () => {
119+
chainResult = await result.current.executeChain([
120+
{ type: 'script', execute: 'true' },
121+
{ type: 'script', execute: 'true' },
122+
]);
123+
});
124+
125+
await waitFor(() => {
126+
expect(result.current.loading).toBe(false);
127+
});
128+
129+
expect(chainResult.success).toBe(true);
130+
expect(result.current.result).toEqual(chainResult);
131+
expect(result.current.error).toBeNull();
132+
});
133+
});
134+
135+
describe('ActionProvider updateContext', () => {
136+
it('updateContext is callable', () => {
137+
const wrapper = ({ children }: { children: React.ReactNode }) =>
138+
React.createElement(ActionProvider, { context: {} }, children);
139+
140+
const { result } = renderHook(() => useAction(), { wrapper });
141+
142+
expect(() => {
143+
result.current.updateContext({ record: { id: 1 } });
144+
}).not.toThrow();
145+
});
146+
});
147+
148+
describe('ActionProvider with handlers', () => {
149+
it('invokes onToast handler when a toast action is executed', async () => {
150+
const onToast = vi.fn();
151+
const onNavigate = vi.fn();
152+
153+
const wrapper = ({ children }: { children: React.ReactNode }) =>
154+
React.createElement(
155+
ActionProvider,
156+
{ context: {}, onToast, onNavigate },
157+
children,
158+
);
159+
160+
const { result } = renderHook(() => useAction(), { wrapper });
161+
162+
await act(async () => {
163+
await result.current.execute({
164+
type: 'script',
165+
execute: 'true',
166+
toast: { showOnSuccess: true },
167+
successMessage: 'Saved!',
168+
});
169+
});
170+
171+
expect(onToast).toHaveBeenCalledWith('Saved!', expect.objectContaining({ type: 'success' }));
172+
});
173+
});
174+
});

0 commit comments

Comments
 (0)