Skip to content

Commit 2f23478

Browse files
authored
Merge pull request #67 from DouglasNeuroInformatics/actions
feat: add useDestructiveAction and CoreProvider
2 parents e0a589a + 8ac9e0e commit 2f23478

18 files changed

+253
-47
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"types": "./dist/i18n.d.ts",
3232
"import": "./dist/i18n.js"
3333
},
34+
"./providers": {
35+
"types": "./dist/providers.d.ts",
36+
"import": "./dist/providers.js"
37+
},
3438
"./package.json": "./package.json",
3539
"./tailwind/globals.css": "./dist/tailwind/globals.css",
3640
"./utils": {

src/components/NotificationHub/NotificationHub.stories.tsx

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/components/NotificationHub/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/components/OneTimePasswordInput/OneTimePasswordInput.stories.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Meta, StoryObj } from '@storybook/react-vite';
22

3-
import { NotificationHub } from '../NotificationHub';
3+
import { CoreProvider } from '@/providers/CoreProvider';
4+
45
import { OneTimePasswordInput } from './OneTimePasswordInput';
56

67
type Story = StoryObj<typeof OneTimePasswordInput>;
@@ -15,10 +16,9 @@ export default {
1516
decorators: [
1617
(Story) => {
1718
return (
18-
<>
19-
<NotificationHub />
19+
<CoreProvider>
2020
<Story />
21-
</>
21+
</CoreProvider>
2222
);
2323
}
2424
]

src/components/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export * from './LanguageToggle';
3232
export * from './LineGraph';
3333
export * from './ListboxDropdown';
3434
export * from './MenuBar';
35-
export * from './NotificationHub';
3635
export * from './OneTimePasswordInput';
3736
export * from './Popover';
3837
export * from './Progress';
@@ -53,3 +52,5 @@ export * from './Tabs';
5352
export * from './TextArea';
5453
export * from './ThemeToggle';
5554
export * from './Tooltip';
55+
56+
export { /** @deprecated */ NotificationHub } from '@/providers/CoreProvider/NotificationHub';

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './useChart';
2+
export * from './useDestructiveAction';
23
export * from './useDownload';
34
export * from './useEventCallback';
45
export * from './useEventListener';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useDestructiveAction';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useCallback } from 'react';
2+
3+
import { useDestructiveActionStore } from './useDestructiveActionStore';
4+
5+
import type { DestructiveAction } from './useDestructiveActionStore';
6+
7+
export function useDestructiveAction(destructiveAction: DestructiveAction) {
8+
const addPendingDestructiveAction = useDestructiveActionStore((store) => store.addPendingDestructiveAction);
9+
return useCallback(() => {
10+
addPendingDestructiveAction(destructiveAction);
11+
}, [destructiveAction, addPendingDestructiveAction]);
12+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { act, renderHook } from '@testing-library/react';
2+
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
3+
import * as zustand from 'zustand';
4+
5+
import { useDestructiveActionStore } from './useDestructiveActionStore';
6+
7+
import type { DestructiveAction } from './useDestructiveActionStore';
8+
9+
describe('useDestructiveActionStore', () => {
10+
beforeAll(() => {
11+
vi.spyOn(zustand, 'create');
12+
});
13+
14+
afterEach(() => {
15+
vi.clearAllMocks();
16+
});
17+
18+
it('should render and return an object', () => {
19+
const { result } = renderHook(() => useDestructiveActionStore());
20+
expect(result.current).toBeTypeOf('object');
21+
});
22+
23+
it('should have initial empty pendingDestructiveActions array', () => {
24+
const { result } = renderHook(() => useDestructiveActionStore());
25+
expect(result.current.pendingDestructiveActions).toEqual([]);
26+
});
27+
28+
describe('addPendingDestructiveAction', () => {
29+
it('should add a single action to the array', () => {
30+
const { result } = renderHook(() => useDestructiveActionStore());
31+
const testAction: DestructiveAction = vi.fn();
32+
act(() => {
33+
result.current.addPendingDestructiveAction(testAction);
34+
});
35+
expect(result.current.pendingDestructiveActions).toEqual([testAction]);
36+
});
37+
38+
it('should add multiple actions to the array', () => {
39+
const { result } = renderHook(() => useDestructiveActionStore());
40+
const testAction1: DestructiveAction = vi.fn();
41+
const testAction2: DestructiveAction = vi.fn();
42+
act(() => {
43+
result.current.addPendingDestructiveAction(testAction1);
44+
result.current.addPendingDestructiveAction(testAction2);
45+
});
46+
expect(result.current.pendingDestructiveActions).toEqual([testAction1, testAction2]);
47+
});
48+
});
49+
50+
describe('deletePendingDestructiveAction', () => {
51+
it('should remove a specific action from the array', () => {
52+
const { result } = renderHook(() => useDestructiveActionStore());
53+
const testAction1: DestructiveAction = vi.fn();
54+
const testAction2: DestructiveAction = vi.fn();
55+
const testAction3: DestructiveAction = vi.fn();
56+
57+
act(() => {
58+
result.current.addPendingDestructiveAction(testAction1);
59+
result.current.addPendingDestructiveAction(testAction2);
60+
result.current.addPendingDestructiveAction(testAction3);
61+
});
62+
63+
act(() => {
64+
result.current.deletePendingDestructiveAction(testAction2);
65+
});
66+
67+
expect(result.current.pendingDestructiveActions).toEqual([testAction1, testAction3]);
68+
});
69+
70+
it('should handle removing from empty array', () => {
71+
const { result } = renderHook(() => useDestructiveActionStore());
72+
const testAction: DestructiveAction = vi.fn();
73+
act(() => {
74+
result.current.deletePendingDestructiveAction(testAction);
75+
});
76+
expect(result.current.pendingDestructiveActions).toEqual([]);
77+
});
78+
});
79+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Promisable } from 'type-fest';
2+
import { create } from 'zustand';
3+
4+
export type DestructiveAction = () => Promisable<void>;
5+
6+
export type DestructiveActionStore = {
7+
addPendingDestructiveAction: (action: DestructiveAction) => void;
8+
deletePendingDestructiveAction: (action: DestructiveAction) => void;
9+
pendingDestructiveActions: DestructiveAction[];
10+
};
11+
12+
export const useDestructiveActionStore = create<DestructiveActionStore>((set) => ({
13+
addPendingDestructiveAction: (action) => {
14+
set((state) => ({
15+
pendingDestructiveActions: [...state.pendingDestructiveActions, action]
16+
}));
17+
},
18+
deletePendingDestructiveAction: (action) => {
19+
set((state) => ({
20+
...state,
21+
pendingDestructiveActions: state.pendingDestructiveActions.filter((_action) => _action !== action)
22+
}));
23+
},
24+
pendingDestructiveActions: []
25+
}));

0 commit comments

Comments
 (0)