@sudobility/components-rn 1.0.22 → 1.0.24

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.
@@ -0,0 +1,181 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, act } from '@testing-library/react-native';
3
+ import { Text, Pressable } from 'react-native';
4
+ import { Toast, ToastProvider, useToast } from '../ui/Toast';
5
+ import type { ToastMessage } from '../ui/Toast';
6
+
7
+ describe('Toast', () => {
8
+ const baseToast: ToastMessage = {
9
+ id: 'test-1',
10
+ title: 'Test Toast',
11
+ description: 'This is a test toast',
12
+ variant: 'default',
13
+ };
14
+
15
+ it('renders title', () => {
16
+ render(<Toast toast={baseToast} onRemove={jest.fn()} />);
17
+ expect(screen.getByText('Test Toast')).toBeTruthy();
18
+ });
19
+
20
+ it('renders description', () => {
21
+ render(<Toast toast={baseToast} onRemove={jest.fn()} />);
22
+ expect(screen.getByText('This is a test toast')).toBeTruthy();
23
+ });
24
+
25
+ it('renders close button', () => {
26
+ render(<Toast toast={baseToast} onRemove={jest.fn()} />);
27
+ const closeButton = screen.getByLabelText('Close notification');
28
+ expect(closeButton).toBeTruthy();
29
+ });
30
+
31
+ it('calls onRemove when close button is pressed', () => {
32
+ const onRemove = jest.fn();
33
+ render(<Toast toast={baseToast} onRemove={onRemove} />);
34
+ fireEvent.press(screen.getByLabelText('Close notification'));
35
+ expect(onRemove).toHaveBeenCalledWith('test-1');
36
+ });
37
+
38
+ it('renders action button when action is provided', () => {
39
+ const onAction = jest.fn();
40
+ const toastWithAction: ToastMessage = {
41
+ ...baseToast,
42
+ action: { label: 'Undo', onPress: onAction },
43
+ };
44
+ render(<Toast toast={toastWithAction} onRemove={jest.fn()} />);
45
+ expect(screen.getByText('Undo')).toBeTruthy();
46
+ fireEvent.press(screen.getByText('Undo'));
47
+ expect(onAction).toHaveBeenCalledTimes(1);
48
+ });
49
+
50
+ it('renders with different variants without crashing', () => {
51
+ const variants = [
52
+ 'default',
53
+ 'success',
54
+ 'error',
55
+ 'warning',
56
+ 'info',
57
+ ] as const;
58
+
59
+ variants.forEach(variant => {
60
+ const toast: ToastMessage = { ...baseToast, variant, id: variant };
61
+ const { unmount } = render(<Toast toast={toast} onRemove={jest.fn()} />);
62
+ expect(screen.getByText('Test Toast')).toBeTruthy();
63
+ unmount();
64
+ });
65
+ });
66
+
67
+ it('renders toast without title', () => {
68
+ const toast: ToastMessage = {
69
+ id: 'no-title',
70
+ description: 'Description only',
71
+ };
72
+ render(<Toast toast={toast} onRemove={jest.fn()} />);
73
+ expect(screen.getByText('Description only')).toBeTruthy();
74
+ });
75
+
76
+ it('renders toast without description', () => {
77
+ const toast: ToastMessage = {
78
+ id: 'no-desc',
79
+ title: 'Title only',
80
+ };
81
+ render(<Toast toast={toast} onRemove={jest.fn()} />);
82
+ expect(screen.getByText('Title only')).toBeTruthy();
83
+ });
84
+ });
85
+
86
+ describe('ToastProvider', () => {
87
+ it('renders children', () => {
88
+ render(
89
+ <ToastProvider>
90
+ <Text>App content</Text>
91
+ </ToastProvider>
92
+ );
93
+ expect(screen.getByText('App content')).toBeTruthy();
94
+ });
95
+ });
96
+
97
+ describe('useToast', () => {
98
+ it('throws when used outside ToastProvider', () => {
99
+ // Suppress console.error for this test since we expect an error
100
+ const consoleSpy = jest
101
+ .spyOn(console, 'error')
102
+ .mockImplementation(() => {});
103
+
104
+ const BadComponent = () => {
105
+ useToast();
106
+ return null;
107
+ };
108
+
109
+ expect(() => render(<BadComponent />)).toThrow(
110
+ 'useToast must be used within ToastProvider'
111
+ );
112
+
113
+ consoleSpy.mockRestore();
114
+ });
115
+
116
+ it('addToast adds a toast that renders', () => {
117
+ const TestComponent = () => {
118
+ const { addToast } = useToast();
119
+ return (
120
+ <Pressable
121
+ testID='add-toast'
122
+ onPress={() =>
123
+ addToast({
124
+ title: 'New Toast',
125
+ description: 'Toast was added',
126
+ variant: 'success',
127
+ })
128
+ }
129
+ >
130
+ <Text>Add Toast</Text>
131
+ </Pressable>
132
+ );
133
+ };
134
+
135
+ render(
136
+ <ToastProvider>
137
+ <TestComponent />
138
+ </ToastProvider>
139
+ );
140
+
141
+ act(() => {
142
+ fireEvent.press(screen.getByTestId('add-toast'));
143
+ });
144
+
145
+ expect(screen.getByText('New Toast')).toBeTruthy();
146
+ expect(screen.getByText('Toast was added')).toBeTruthy();
147
+ });
148
+
149
+ it('removeToast removes a toast', () => {
150
+ let toastContext: ReturnType<typeof useToast>;
151
+
152
+ const TestComponent = () => {
153
+ toastContext = useToast();
154
+ return <Text>Test</Text>;
155
+ };
156
+
157
+ render(
158
+ <ToastProvider>
159
+ <TestComponent />
160
+ </ToastProvider>
161
+ );
162
+
163
+ // Add a toast first
164
+ act(() => {
165
+ toastContext.addToast({
166
+ title: 'Removable Toast',
167
+ variant: 'info',
168
+ });
169
+ });
170
+
171
+ expect(screen.getByText('Removable Toast')).toBeTruthy();
172
+
173
+ // Now remove it
174
+ act(() => {
175
+ const toastId = toastContext.toasts[0].id;
176
+ toastContext.removeToast(toastId);
177
+ });
178
+
179
+ expect(screen.queryByText('Removable Toast')).toBeNull();
180
+ });
181
+ });
@@ -0,0 +1,47 @@
1
+ import { cn } from '../lib/utils';
2
+
3
+ describe('cn utility', () => {
4
+ it('merges class names', () => {
5
+ expect(cn('foo', 'bar')).toBe('foo bar');
6
+ });
7
+
8
+ it('handles undefined and null values', () => {
9
+ expect(cn('foo', undefined, 'bar', null)).toBe('foo bar');
10
+ });
11
+
12
+ it('handles empty string', () => {
13
+ expect(cn('')).toBe('');
14
+ });
15
+
16
+ it('handles no arguments', () => {
17
+ expect(cn()).toBe('');
18
+ });
19
+
20
+ it('handles conditional classes via objects', () => {
21
+ expect(cn({ foo: true, bar: false, baz: true })).toBe('foo baz');
22
+ });
23
+
24
+ it('handles arrays', () => {
25
+ expect(cn(['foo', 'bar'])).toBe('foo bar');
26
+ });
27
+
28
+ it('handles mixed arguments', () => {
29
+ expect(cn('base', { active: true, hidden: false }, ['extra'])).toBe(
30
+ 'base active extra'
31
+ );
32
+ });
33
+
34
+ it('handles false and zero values', () => {
35
+ expect(cn('foo', false, 0, 'bar')).toBe('foo bar');
36
+ });
37
+
38
+ it('combines Tailwind-like classes', () => {
39
+ expect(cn('rounded-lg p-4', 'bg-white', 'mt-2')).toBe(
40
+ 'rounded-lg p-4 bg-white mt-2'
41
+ );
42
+ });
43
+
44
+ it('handles nested arrays', () => {
45
+ expect(cn(['a', ['b', 'c']])).toBe('a b c');
46
+ });
47
+ });
package/src/index.ts CHANGED
@@ -187,7 +187,10 @@ export * from './ui/ScrollSpy';
187
187
  export * from './ui/SplitPane';
188
188
  export * from './ui/VirtualList';
189
189
 
190
- // Re-export design system for convenience
190
+ /**
191
+ * @deprecated Import directly from `@sudobility/design` instead.
192
+ * These re-exports will be removed in a future major version.
193
+ */
191
194
  export {
192
195
  variants,
193
196
  textVariants,
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
  import { View, Text, type ViewProps } from 'react-native';
3
- import { cn } from '../lib/utils';
3
+ import { cn } from '../../lib/utils';
4
4
 
5
5
  export type ChainType = 'evm' | 'solana' | 'bitcoin' | 'cosmos';
6
6
 
7
+ /** Badge displaying a blockchain network identifier with chain-specific color and icon */
7
8
  export interface ChainBadgeProps extends ViewProps {
8
9
  chainType: ChainType;
9
10
  size?: 'sm' | 'md' | 'lg';
@@ -0,0 +1 @@
1
+ export { ChainBadge, type ChainBadgeProps, type ChainType } from './ChainBadge';
@@ -1 +0,0 @@
1
- {"version":3,"file":"ChainBadge.d.ts","sourceRoot":"","sources":["../../src/ui/ChainBadge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAG1D,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEhE,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,SAAS,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA0CD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAgChD,CAAC"}