@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.
- 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/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/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/ui/{ChainBadge.tsx → ChainBadge/ChainBadge.tsx} +2 -1
- package/src/ui/ChainBadge/index.ts +1 -0
- package/dist/ui/ChainBadge.d.ts.map +0 -1
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/Tabs';
|
|
5
|
+
|
|
6
|
+
describe('Tabs', () => {
|
|
7
|
+
const renderTabs = (props = {}) =>
|
|
8
|
+
render(
|
|
9
|
+
<Tabs defaultValue='tab1' {...props}>
|
|
10
|
+
<TabsList>
|
|
11
|
+
<TabsTrigger value='tab1'>Tab 1</TabsTrigger>
|
|
12
|
+
<TabsTrigger value='tab2'>Tab 2</TabsTrigger>
|
|
13
|
+
<TabsTrigger value='tab3'>Tab 3</TabsTrigger>
|
|
14
|
+
</TabsList>
|
|
15
|
+
<TabsContent value='tab1'>
|
|
16
|
+
<Text>Content 1</Text>
|
|
17
|
+
</TabsContent>
|
|
18
|
+
<TabsContent value='tab2'>
|
|
19
|
+
<Text>Content 2</Text>
|
|
20
|
+
</TabsContent>
|
|
21
|
+
<TabsContent value='tab3'>
|
|
22
|
+
<Text>Content 3</Text>
|
|
23
|
+
</TabsContent>
|
|
24
|
+
</Tabs>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
it('renders tab triggers', () => {
|
|
28
|
+
renderTabs();
|
|
29
|
+
expect(screen.getByText('Tab 1')).toBeTruthy();
|
|
30
|
+
expect(screen.getByText('Tab 2')).toBeTruthy();
|
|
31
|
+
expect(screen.getByText('Tab 3')).toBeTruthy();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('shows content for default tab', () => {
|
|
35
|
+
renderTabs();
|
|
36
|
+
expect(screen.getByText('Content 1')).toBeTruthy();
|
|
37
|
+
expect(screen.queryByText('Content 2')).toBeNull();
|
|
38
|
+
expect(screen.queryByText('Content 3')).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('switches content when a tab is pressed', () => {
|
|
42
|
+
renderTabs();
|
|
43
|
+
|
|
44
|
+
// Initially tab 1 content is shown
|
|
45
|
+
expect(screen.getByText('Content 1')).toBeTruthy();
|
|
46
|
+
|
|
47
|
+
// Press tab 2
|
|
48
|
+
fireEvent.press(screen.getByText('Tab 2'));
|
|
49
|
+
expect(screen.queryByText('Content 1')).toBeNull();
|
|
50
|
+
expect(screen.getByText('Content 2')).toBeTruthy();
|
|
51
|
+
|
|
52
|
+
// Press tab 3
|
|
53
|
+
fireEvent.press(screen.getByText('Tab 3'));
|
|
54
|
+
expect(screen.queryByText('Content 2')).toBeNull();
|
|
55
|
+
expect(screen.getByText('Content 3')).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('calls onValueChange when tab changes', () => {
|
|
59
|
+
const onValueChange = jest.fn();
|
|
60
|
+
renderTabs({ onValueChange });
|
|
61
|
+
|
|
62
|
+
fireEvent.press(screen.getByText('Tab 2'));
|
|
63
|
+
expect(onValueChange).toHaveBeenCalledWith('tab2');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('works in controlled mode', () => {
|
|
67
|
+
const onValueChange = jest.fn();
|
|
68
|
+
const { rerender } = render(
|
|
69
|
+
<Tabs value='tab1' onValueChange={onValueChange}>
|
|
70
|
+
<TabsList>
|
|
71
|
+
<TabsTrigger value='tab1'>Tab 1</TabsTrigger>
|
|
72
|
+
<TabsTrigger value='tab2'>Tab 2</TabsTrigger>
|
|
73
|
+
</TabsList>
|
|
74
|
+
<TabsContent value='tab1'>
|
|
75
|
+
<Text>Content 1</Text>
|
|
76
|
+
</TabsContent>
|
|
77
|
+
<TabsContent value='tab2'>
|
|
78
|
+
<Text>Content 2</Text>
|
|
79
|
+
</TabsContent>
|
|
80
|
+
</Tabs>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(screen.getByText('Content 1')).toBeTruthy();
|
|
84
|
+
|
|
85
|
+
// Press tab 2 - in controlled mode the parent decides
|
|
86
|
+
fireEvent.press(screen.getByText('Tab 2'));
|
|
87
|
+
expect(onValueChange).toHaveBeenCalledWith('tab2');
|
|
88
|
+
|
|
89
|
+
// Content doesn't change until the parent updates the value prop
|
|
90
|
+
expect(screen.getByText('Content 1')).toBeTruthy();
|
|
91
|
+
|
|
92
|
+
// Rerender with updated value
|
|
93
|
+
rerender(
|
|
94
|
+
<Tabs value='tab2' onValueChange={onValueChange}>
|
|
95
|
+
<TabsList>
|
|
96
|
+
<TabsTrigger value='tab1'>Tab 1</TabsTrigger>
|
|
97
|
+
<TabsTrigger value='tab2'>Tab 2</TabsTrigger>
|
|
98
|
+
</TabsList>
|
|
99
|
+
<TabsContent value='tab1'>
|
|
100
|
+
<Text>Content 1</Text>
|
|
101
|
+
</TabsContent>
|
|
102
|
+
<TabsContent value='tab2'>
|
|
103
|
+
<Text>Content 2</Text>
|
|
104
|
+
</TabsContent>
|
|
105
|
+
</Tabs>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(screen.getByText('Content 2')).toBeTruthy();
|
|
109
|
+
expect(screen.queryByText('Content 1')).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('tab triggers have tab accessibility role', () => {
|
|
113
|
+
renderTabs();
|
|
114
|
+
const tabs = screen.getAllByRole('tab');
|
|
115
|
+
expect(tabs.length).toBe(3);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('selected tab has selected accessibility state', () => {
|
|
119
|
+
renderTabs();
|
|
120
|
+
const tabs = screen.getAllByRole('tab');
|
|
121
|
+
// First tab should be selected
|
|
122
|
+
expect(tabs[0].props.accessibilityState).toEqual({
|
|
123
|
+
selected: true,
|
|
124
|
+
disabled: false,
|
|
125
|
+
});
|
|
126
|
+
// Second tab should not be selected
|
|
127
|
+
expect(tabs[1].props.accessibilityState).toEqual({
|
|
128
|
+
selected: false,
|
|
129
|
+
disabled: false,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('disabled tab has disabled accessibility state', () => {
|
|
134
|
+
render(
|
|
135
|
+
<Tabs defaultValue='tab1'>
|
|
136
|
+
<TabsList>
|
|
137
|
+
<TabsTrigger value='tab1'>Tab 1</TabsTrigger>
|
|
138
|
+
<TabsTrigger value='tab2' disabled>
|
|
139
|
+
Tab 2
|
|
140
|
+
</TabsTrigger>
|
|
141
|
+
</TabsList>
|
|
142
|
+
<TabsContent value='tab1'>
|
|
143
|
+
<Text>Content 1</Text>
|
|
144
|
+
</TabsContent>
|
|
145
|
+
<TabsContent value='tab2'>
|
|
146
|
+
<Text>Content 2</Text>
|
|
147
|
+
</TabsContent>
|
|
148
|
+
</Tabs>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const tabs = screen.getAllByRole('tab');
|
|
152
|
+
expect(tabs[1].props.accessibilityState).toEqual({
|
|
153
|
+
selected: false,
|
|
154
|
+
disabled: true,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('disabled tab does not switch content when pressed', () => {
|
|
159
|
+
render(
|
|
160
|
+
<Tabs defaultValue='tab1'>
|
|
161
|
+
<TabsList>
|
|
162
|
+
<TabsTrigger value='tab1'>Tab 1</TabsTrigger>
|
|
163
|
+
<TabsTrigger value='tab2' disabled>
|
|
164
|
+
Tab 2
|
|
165
|
+
</TabsTrigger>
|
|
166
|
+
</TabsList>
|
|
167
|
+
<TabsContent value='tab1'>
|
|
168
|
+
<Text>Content 1</Text>
|
|
169
|
+
</TabsContent>
|
|
170
|
+
<TabsContent value='tab2'>
|
|
171
|
+
<Text>Content 2</Text>
|
|
172
|
+
</TabsContent>
|
|
173
|
+
</Tabs>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
fireEvent.press(screen.getByText('Tab 2'));
|
|
177
|
+
// Content 1 should still be visible
|
|
178
|
+
expect(screen.getByText('Content 1')).toBeTruthy();
|
|
179
|
+
expect(screen.queryByText('Content 2')).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('TabsTrigger outside Tabs context', () => {
|
|
184
|
+
it('throws when used outside Tabs', () => {
|
|
185
|
+
const consoleSpy = jest
|
|
186
|
+
.spyOn(console, 'error')
|
|
187
|
+
.mockImplementation(() => {});
|
|
188
|
+
|
|
189
|
+
expect(() => render(<TabsTrigger value='tab1'>Tab 1</TabsTrigger>)).toThrow(
|
|
190
|
+
'Tabs components must be used within a Tabs provider'
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
consoleSpy.mockRestore();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('TabsContent outside Tabs context', () => {
|
|
198
|
+
it('throws when used outside Tabs', () => {
|
|
199
|
+
const consoleSpy = jest
|
|
200
|
+
.spyOn(console, 'error')
|
|
201
|
+
.mockImplementation(() => {});
|
|
202
|
+
|
|
203
|
+
expect(() =>
|
|
204
|
+
render(
|
|
205
|
+
<TabsContent value='tab1'>
|
|
206
|
+
<Text>Content</Text>
|
|
207
|
+
</TabsContent>
|
|
208
|
+
)
|
|
209
|
+
).toThrow('Tabs components must be used within a Tabs provider');
|
|
210
|
+
|
|
211
|
+
consoleSpy.mockRestore();
|
|
212
|
+
});
|
|
213
|
+
});
|