@sudobility/components-rn 1.0.21 → 1.0.23
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/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/lib/utils.d.ts +7 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/ui/Alert/Alert.d.ts +8 -0
- package/dist/ui/Alert/Alert.d.ts.map +1 -1
- package/dist/ui/Button/Button.shared.d.ts +18 -3
- package/dist/ui/Button/Button.shared.d.ts.map +1 -1
- package/dist/ui/Card/Card.d.ts +32 -0
- package/dist/ui/Card/Card.d.ts.map +1 -1
- package/dist/ui/{ChainBadge.d.ts → ChainBadge/ChainBadge.d.ts} +1 -0
- package/dist/ui/ChainBadge/ChainBadge.d.ts.map +1 -0
- package/dist/ui/ChainBadge/index.d.ts +2 -0
- package/dist/ui/ChainBadge/index.d.ts.map +1 -0
- package/dist/ui/Spinner/Spinner.d.ts +8 -0
- package/dist/ui/Spinner/Spinner.d.ts.map +1 -1
- package/dist/ui/Toast/Toast.d.ts +11 -0
- package/dist/ui/Toast/Toast.d.ts.map +1 -1
- package/package.json +23 -12
- package/src/__tests__/alert.test.tsx +95 -0
- package/src/__tests__/badge.test.tsx +121 -0
- package/src/__tests__/button.test.tsx +107 -0
- package/src/__tests__/card.test.tsx +149 -0
- package/src/__tests__/dialog.test.tsx +76 -0
- package/src/__tests__/input.test.tsx +80 -0
- package/src/__tests__/modal.test.tsx +125 -0
- package/src/__tests__/sheet.test.tsx +113 -0
- package/src/__tests__/tabs.test.tsx +213 -0
- package/src/__tests__/toast.test.tsx +181 -0
- package/src/__tests__/utils.test.ts +47 -0
- package/src/index.ts +4 -1
- package/src/lib/utils.ts +7 -2
- package/src/ui/Alert/Alert.tsx +8 -0
- package/src/ui/Button/Button.shared.ts +18 -3
- package/src/ui/Card/Card.tsx +32 -0
- package/src/ui/{ChainBadge.tsx → ChainBadge/ChainBadge.tsx} +2 -1
- package/src/ui/ChainBadge/index.ts +1 -0
- package/src/ui/Spinner/Spinner.tsx +8 -0
- package/src/ui/Toast/Toast.tsx +11 -0
- package/dist/ui/ChainBadge.d.ts.map +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Card, CardHeader, CardContent, CardFooter } from '../ui/Card';
|
|
5
|
+
|
|
6
|
+
// Mock @sudobility/design
|
|
7
|
+
jest.mock('@sudobility/design', () => ({
|
|
8
|
+
textVariants: {
|
|
9
|
+
heading: { h4: () => 'mocked-heading' },
|
|
10
|
+
body: { sm: () => 'mocked-body' },
|
|
11
|
+
},
|
|
12
|
+
getCardVariantColors: () => 'mocked-card-variant',
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
describe('Card', () => {
|
|
16
|
+
it('renders children', () => {
|
|
17
|
+
render(
|
|
18
|
+
<Card>
|
|
19
|
+
<Text>Card content</Text>
|
|
20
|
+
</Card>
|
|
21
|
+
);
|
|
22
|
+
expect(screen.getByText('Card content')).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders with different variants without crashing', () => {
|
|
26
|
+
const variants = [
|
|
27
|
+
'default',
|
|
28
|
+
'bordered',
|
|
29
|
+
'elevated',
|
|
30
|
+
'info',
|
|
31
|
+
'success',
|
|
32
|
+
'warning',
|
|
33
|
+
'error',
|
|
34
|
+
'callout',
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
variants.forEach(variant => {
|
|
38
|
+
const { unmount } = render(
|
|
39
|
+
<Card variant={variant}>
|
|
40
|
+
<Text>{variant}</Text>
|
|
41
|
+
</Card>
|
|
42
|
+
);
|
|
43
|
+
expect(screen.getByText(variant)).toBeTruthy();
|
|
44
|
+
unmount();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('renders icon for info-type variants', () => {
|
|
49
|
+
render(
|
|
50
|
+
<Card variant='info' icon={<Text testID='card-icon'>Icon</Text>}>
|
|
51
|
+
<Text>Info card</Text>
|
|
52
|
+
</Card>
|
|
53
|
+
);
|
|
54
|
+
expect(screen.getByTestId('card-icon')).toBeTruthy();
|
|
55
|
+
expect(screen.getByText('Info card')).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('renders close button for info-type variants with onClose', () => {
|
|
59
|
+
const onClose = jest.fn();
|
|
60
|
+
render(
|
|
61
|
+
<Card variant='error' onClose={onClose}>
|
|
62
|
+
<Text>Error card</Text>
|
|
63
|
+
</Card>
|
|
64
|
+
);
|
|
65
|
+
const closeButton = screen.getByLabelText('Close');
|
|
66
|
+
expect(closeButton).toBeTruthy();
|
|
67
|
+
fireEvent.press(closeButton);
|
|
68
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('does not render icon/close layout for non-info variants', () => {
|
|
72
|
+
render(
|
|
73
|
+
<Card variant='elevated'>
|
|
74
|
+
<Text>Plain card</Text>
|
|
75
|
+
</Card>
|
|
76
|
+
);
|
|
77
|
+
expect(screen.queryByLabelText('Close')).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('CardHeader', () => {
|
|
82
|
+
it('renders title', () => {
|
|
83
|
+
render(<CardHeader title='Card Title' />);
|
|
84
|
+
expect(screen.getByText('Card Title')).toBeTruthy();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('renders description', () => {
|
|
88
|
+
render(<CardHeader description='Card description' />);
|
|
89
|
+
expect(screen.getByText('Card description')).toBeTruthy();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('renders title and description together', () => {
|
|
93
|
+
render(<CardHeader title='Title' description='Description' />);
|
|
94
|
+
expect(screen.getByText('Title')).toBeTruthy();
|
|
95
|
+
expect(screen.getByText('Description')).toBeTruthy();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders children alongside title', () => {
|
|
99
|
+
render(
|
|
100
|
+
<CardHeader title='Title'>
|
|
101
|
+
<Text>Extra content</Text>
|
|
102
|
+
</CardHeader>
|
|
103
|
+
);
|
|
104
|
+
expect(screen.getByText('Title')).toBeTruthy();
|
|
105
|
+
expect(screen.getByText('Extra content')).toBeTruthy();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('CardContent', () => {
|
|
110
|
+
it('renders children', () => {
|
|
111
|
+
render(
|
|
112
|
+
<CardContent>
|
|
113
|
+
<Text>Body content</Text>
|
|
114
|
+
</CardContent>
|
|
115
|
+
);
|
|
116
|
+
expect(screen.getByText('Body content')).toBeTruthy();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('CardFooter', () => {
|
|
121
|
+
it('renders children', () => {
|
|
122
|
+
render(
|
|
123
|
+
<CardFooter>
|
|
124
|
+
<Text>Footer content</Text>
|
|
125
|
+
</CardFooter>
|
|
126
|
+
);
|
|
127
|
+
expect(screen.getByText('Footer content')).toBeTruthy();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('Card composition', () => {
|
|
132
|
+
it('renders full card with all sub-components', () => {
|
|
133
|
+
render(
|
|
134
|
+
<Card variant='elevated'>
|
|
135
|
+
<CardHeader title='My Card' description='A description' />
|
|
136
|
+
<CardContent>
|
|
137
|
+
<Text>Main body</Text>
|
|
138
|
+
</CardContent>
|
|
139
|
+
<CardFooter>
|
|
140
|
+
<Text>Action area</Text>
|
|
141
|
+
</CardFooter>
|
|
142
|
+
</Card>
|
|
143
|
+
);
|
|
144
|
+
expect(screen.getByText('My Card')).toBeTruthy();
|
|
145
|
+
expect(screen.getByText('A description')).toBeTruthy();
|
|
146
|
+
expect(screen.getByText('Main body')).toBeTruthy();
|
|
147
|
+
expect(screen.getByText('Action area')).toBeTruthy();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Dialog } from '../ui/Dialog';
|
|
5
|
+
|
|
6
|
+
describe('Dialog', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
isOpen: true,
|
|
9
|
+
onClose: jest.fn(),
|
|
10
|
+
children: <Text>Dialog content</Text>,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders children when open', () => {
|
|
18
|
+
render(<Dialog {...defaultProps} />);
|
|
19
|
+
expect(screen.getByText('Dialog content')).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders close button by default when onClose is provided', () => {
|
|
23
|
+
render(<Dialog {...defaultProps} />);
|
|
24
|
+
const closeButton = screen.getByLabelText('Close dialog');
|
|
25
|
+
expect(closeButton).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('calls onClose when close button is pressed', () => {
|
|
29
|
+
const onClose = jest.fn();
|
|
30
|
+
render(
|
|
31
|
+
<Dialog isOpen={true} onClose={onClose}>
|
|
32
|
+
<Text>Content</Text>
|
|
33
|
+
</Dialog>
|
|
34
|
+
);
|
|
35
|
+
fireEvent.press(screen.getByLabelText('Close dialog'));
|
|
36
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('hides close button when showCloseButton is false', () => {
|
|
40
|
+
render(<Dialog {...defaultProps} showCloseButton={false} />);
|
|
41
|
+
expect(screen.queryByLabelText('Close dialog')).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders with different sizes without crashing', () => {
|
|
45
|
+
const sizes = ['sm', 'md', 'lg', 'xl', 'full'] as const;
|
|
46
|
+
|
|
47
|
+
sizes.forEach(size => {
|
|
48
|
+
const { unmount } = render(
|
|
49
|
+
<Dialog {...defaultProps} size={size}>
|
|
50
|
+
<Text>{size}</Text>
|
|
51
|
+
</Dialog>
|
|
52
|
+
);
|
|
53
|
+
expect(screen.getByText(size)).toBeTruthy();
|
|
54
|
+
unmount();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not render close button when onClose is not provided', () => {
|
|
59
|
+
render(
|
|
60
|
+
<Dialog isOpen={true}>
|
|
61
|
+
<Text>No close</Text>
|
|
62
|
+
</Dialog>
|
|
63
|
+
);
|
|
64
|
+
expect(screen.queryByLabelText('Close dialog')).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('renders without close button when showCloseButton is false', () => {
|
|
68
|
+
render(
|
|
69
|
+
<Dialog {...defaultProps} showCloseButton={false}>
|
|
70
|
+
<Text>No X button</Text>
|
|
71
|
+
</Dialog>
|
|
72
|
+
);
|
|
73
|
+
expect(screen.getByText('No X button')).toBeTruthy();
|
|
74
|
+
expect(screen.queryByLabelText('Close dialog')).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Input } from '../ui/Input';
|
|
4
|
+
|
|
5
|
+
// Mock @sudobility/design
|
|
6
|
+
jest.mock('@sudobility/design', () => ({
|
|
7
|
+
variants: {
|
|
8
|
+
input: {
|
|
9
|
+
default: () => 'mocked-input-class',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('Input', () => {
|
|
15
|
+
it('renders with placeholder', () => {
|
|
16
|
+
render(<Input placeholder='Enter text' />);
|
|
17
|
+
expect(screen.getByPlaceholderText('Enter text')).toBeTruthy();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('displays the current value', () => {
|
|
21
|
+
render(<Input value='Hello' />);
|
|
22
|
+
expect(screen.getByDisplayValue('Hello')).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('calls onChangeText when text changes', () => {
|
|
26
|
+
const onChangeText = jest.fn();
|
|
27
|
+
render(<Input placeholder='Type here' onChangeText={onChangeText} />);
|
|
28
|
+
fireEvent.changeText(screen.getByPlaceholderText('Type here'), 'New text');
|
|
29
|
+
expect(onChangeText).toHaveBeenCalledWith('New text');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('is not editable when disabled', () => {
|
|
33
|
+
render(<Input placeholder='Disabled' disabled />);
|
|
34
|
+
const input = screen.getByPlaceholderText('Disabled');
|
|
35
|
+
expect(input.props.editable).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('is not editable when editable is false', () => {
|
|
39
|
+
render(<Input placeholder='Read only' editable={false} />);
|
|
40
|
+
const input = screen.getByPlaceholderText('Read only');
|
|
41
|
+
expect(input.props.editable).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('sets disabled accessibility state when disabled', () => {
|
|
45
|
+
render(<Input placeholder='Disabled' disabled />);
|
|
46
|
+
const input = screen.getByPlaceholderText('Disabled');
|
|
47
|
+
expect(input.props.accessibilityState).toEqual({ disabled: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('calls onFocus and onBlur handlers', () => {
|
|
51
|
+
const onFocus = jest.fn();
|
|
52
|
+
const onBlur = jest.fn();
|
|
53
|
+
render(
|
|
54
|
+
<Input placeholder='Focus test' onFocus={onFocus} onBlur={onBlur} />
|
|
55
|
+
);
|
|
56
|
+
const input = screen.getByPlaceholderText('Focus test');
|
|
57
|
+
fireEvent(input, 'focus');
|
|
58
|
+
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
59
|
+
fireEvent(input, 'blur');
|
|
60
|
+
expect(onBlur).toHaveBeenCalledTimes(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('renders with error state without crashing', () => {
|
|
64
|
+
render(<Input placeholder='Error input' error />);
|
|
65
|
+
expect(screen.getByPlaceholderText('Error input')).toBeTruthy();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('passes through TextInput props', () => {
|
|
69
|
+
render(
|
|
70
|
+
<Input
|
|
71
|
+
placeholder='Email'
|
|
72
|
+
keyboardType='email-address'
|
|
73
|
+
autoCapitalize='none'
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
const input = screen.getByPlaceholderText('Email');
|
|
77
|
+
expect(input.props.keyboardType).toBe('email-address');
|
|
78
|
+
expect(input.props.autoCapitalize).toBe('none');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Modal, ModalHeader, ModalContent, ModalFooter } from '../ui/Modal';
|
|
5
|
+
|
|
6
|
+
describe('Modal', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
isOpen: true,
|
|
9
|
+
onClose: jest.fn(),
|
|
10
|
+
children: <Text>Modal body</Text>,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders children when open', () => {
|
|
18
|
+
render(<Modal {...defaultProps} />);
|
|
19
|
+
expect(screen.getByText('Modal body')).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders title when provided', () => {
|
|
23
|
+
render(<Modal {...defaultProps} title='My Modal' />);
|
|
24
|
+
expect(screen.getByText('My Modal')).toBeTruthy();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders close button by default', () => {
|
|
28
|
+
render(<Modal {...defaultProps} title='Modal' />);
|
|
29
|
+
const closeButton = screen.getByLabelText('Close modal');
|
|
30
|
+
expect(closeButton).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('calls onClose when close button is pressed', () => {
|
|
34
|
+
const onClose = jest.fn();
|
|
35
|
+
render(
|
|
36
|
+
<Modal isOpen={true} onClose={onClose} title='Modal'>
|
|
37
|
+
<Text>Content</Text>
|
|
38
|
+
</Modal>
|
|
39
|
+
);
|
|
40
|
+
fireEvent.press(screen.getByLabelText('Close modal'));
|
|
41
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('hides close button when showCloseButton is false', () => {
|
|
45
|
+
render(<Modal {...defaultProps} title='Modal' showCloseButton={false} />);
|
|
46
|
+
expect(screen.queryByLabelText('Close modal')).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders with different sizes without crashing', () => {
|
|
50
|
+
const sizes = ['small', 'medium', 'large', 'fullWidth'] as const;
|
|
51
|
+
|
|
52
|
+
sizes.forEach(size => {
|
|
53
|
+
const { unmount } = render(
|
|
54
|
+
<Modal {...defaultProps} size={size}>
|
|
55
|
+
<Text>{size}</Text>
|
|
56
|
+
</Modal>
|
|
57
|
+
);
|
|
58
|
+
expect(screen.getByText(size)).toBeTruthy();
|
|
59
|
+
unmount();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('does not show header when no title and showCloseButton is false', () => {
|
|
64
|
+
const { toJSON } = render(
|
|
65
|
+
<Modal {...defaultProps} showCloseButton={false} />
|
|
66
|
+
);
|
|
67
|
+
expect(screen.queryByLabelText('Close modal')).toBeNull();
|
|
68
|
+
expect(toJSON()).toBeTruthy();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('ModalHeader', () => {
|
|
73
|
+
it('renders children', () => {
|
|
74
|
+
render(
|
|
75
|
+
<ModalHeader>
|
|
76
|
+
<Text>Header content</Text>
|
|
77
|
+
</ModalHeader>
|
|
78
|
+
);
|
|
79
|
+
expect(screen.getByText('Header content')).toBeTruthy();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('ModalContent', () => {
|
|
84
|
+
it('renders children', () => {
|
|
85
|
+
render(
|
|
86
|
+
<ModalContent>
|
|
87
|
+
<Text>Content area</Text>
|
|
88
|
+
</ModalContent>
|
|
89
|
+
);
|
|
90
|
+
expect(screen.getByText('Content area')).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('ModalFooter', () => {
|
|
95
|
+
it('renders children', () => {
|
|
96
|
+
render(
|
|
97
|
+
<ModalFooter>
|
|
98
|
+
<Text>Footer actions</Text>
|
|
99
|
+
</ModalFooter>
|
|
100
|
+
);
|
|
101
|
+
expect(screen.getByText('Footer actions')).toBeTruthy();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('Modal composition', () => {
|
|
106
|
+
it('renders with all sub-components', () => {
|
|
107
|
+
render(
|
|
108
|
+
<Modal isOpen={true} onClose={jest.fn()} title='Full Modal'>
|
|
109
|
+
<ModalHeader>
|
|
110
|
+
<Text>Custom Header</Text>
|
|
111
|
+
</ModalHeader>
|
|
112
|
+
<ModalContent>
|
|
113
|
+
<Text>Main content</Text>
|
|
114
|
+
</ModalContent>
|
|
115
|
+
<ModalFooter>
|
|
116
|
+
<Text>Footer</Text>
|
|
117
|
+
</ModalFooter>
|
|
118
|
+
</Modal>
|
|
119
|
+
);
|
|
120
|
+
expect(screen.getByText('Full Modal')).toBeTruthy();
|
|
121
|
+
expect(screen.getByText('Custom Header')).toBeTruthy();
|
|
122
|
+
expect(screen.getByText('Main content')).toBeTruthy();
|
|
123
|
+
expect(screen.getByText('Footer')).toBeTruthy();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Sheet } from '../ui/Sheet';
|
|
5
|
+
|
|
6
|
+
describe('Sheet', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
isOpen: true,
|
|
9
|
+
onClose: jest.fn(),
|
|
10
|
+
children: <Text>Sheet content</Text>,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders children when open', () => {
|
|
18
|
+
render(<Sheet {...defaultProps} />);
|
|
19
|
+
expect(screen.getByText('Sheet content')).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders title when provided', () => {
|
|
23
|
+
render(<Sheet {...defaultProps} title='Sheet Title' />);
|
|
24
|
+
expect(screen.getByText('Sheet Title')).toBeTruthy();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders description when provided', () => {
|
|
28
|
+
render(
|
|
29
|
+
<Sheet {...defaultProps} title='Title' description='Sheet description' />
|
|
30
|
+
);
|
|
31
|
+
expect(screen.getByText('Sheet description')).toBeTruthy();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders close button by default', () => {
|
|
35
|
+
render(<Sheet {...defaultProps} title='Title' />);
|
|
36
|
+
const closeButton = screen.getByLabelText('Close sheet');
|
|
37
|
+
expect(closeButton).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('calls onClose when close button is pressed', () => {
|
|
41
|
+
const onClose = jest.fn();
|
|
42
|
+
render(
|
|
43
|
+
<Sheet isOpen={true} onClose={onClose} title='Sheet'>
|
|
44
|
+
<Text>Content</Text>
|
|
45
|
+
</Sheet>
|
|
46
|
+
);
|
|
47
|
+
fireEvent.press(screen.getByLabelText('Close sheet'));
|
|
48
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('hides close button when showCloseButton is false', () => {
|
|
52
|
+
render(<Sheet {...defaultProps} title='Title' showCloseButton={false} />);
|
|
53
|
+
expect(screen.queryByLabelText('Close sheet')).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('renders footer when provided', () => {
|
|
57
|
+
render(<Sheet {...defaultProps} footer={<Text>Footer area</Text>} />);
|
|
58
|
+
expect(screen.getByText('Footer area')).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('renders with different sizes without crashing', () => {
|
|
62
|
+
const sizes = ['sm', 'md', 'lg', 'full'] as const;
|
|
63
|
+
|
|
64
|
+
sizes.forEach(size => {
|
|
65
|
+
const { unmount } = render(
|
|
66
|
+
<Sheet {...defaultProps} size={size}>
|
|
67
|
+
<Text>Size {size}</Text>
|
|
68
|
+
</Sheet>
|
|
69
|
+
);
|
|
70
|
+
expect(screen.getByText(`Size ${size}`)).toBeTruthy();
|
|
71
|
+
unmount();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('renders with different sides without crashing', () => {
|
|
76
|
+
const sides = ['bottom', 'top', 'left', 'right'] as const;
|
|
77
|
+
|
|
78
|
+
sides.forEach(side => {
|
|
79
|
+
const { unmount } = render(
|
|
80
|
+
<Sheet {...defaultProps} side={side}>
|
|
81
|
+
<Text>Side {side}</Text>
|
|
82
|
+
</Sheet>
|
|
83
|
+
);
|
|
84
|
+
expect(screen.getByText(`Side ${side}`)).toBeTruthy();
|
|
85
|
+
unmount();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('renders without drag handle when showHandle is false', () => {
|
|
90
|
+
const { toJSON } = render(<Sheet {...defaultProps} showHandle={false} />);
|
|
91
|
+
// When showHandle is false, the drag handle View is not rendered
|
|
92
|
+
expect(screen.getByText('Sheet content')).toBeTruthy();
|
|
93
|
+
expect(toJSON()).toBeTruthy();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('renders with title, description, content, and footer together', () => {
|
|
97
|
+
render(
|
|
98
|
+
<Sheet
|
|
99
|
+
isOpen={true}
|
|
100
|
+
onClose={jest.fn()}
|
|
101
|
+
title='Full Sheet'
|
|
102
|
+
description='A full sheet example'
|
|
103
|
+
footer={<Text>Done</Text>}
|
|
104
|
+
>
|
|
105
|
+
<Text>Main content</Text>
|
|
106
|
+
</Sheet>
|
|
107
|
+
);
|
|
108
|
+
expect(screen.getByText('Full Sheet')).toBeTruthy();
|
|
109
|
+
expect(screen.getByText('A full sheet example')).toBeTruthy();
|
|
110
|
+
expect(screen.getByText('Main content')).toBeTruthy();
|
|
111
|
+
expect(screen.getByText('Done')).toBeTruthy();
|
|
112
|
+
});
|
|
113
|
+
});
|