@astacinco/rn-primitives 0.1.0
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/README.md +195 -0
- package/__tests__/Button.test.tsx +114 -0
- package/__tests__/Card.test.tsx +73 -0
- package/__tests__/Container.test.tsx +71 -0
- package/__tests__/Divider.test.tsx +41 -0
- package/__tests__/Input.test.tsx +69 -0
- package/__tests__/Stack.test.tsx +85 -0
- package/__tests__/Text.test.tsx +64 -0
- package/__tests__/__snapshots__/Button.test.tsx.snap +143 -0
- package/__tests__/__snapshots__/Card.test.tsx.snap +47 -0
- package/__tests__/__snapshots__/Container.test.tsx.snap +51 -0
- package/__tests__/__snapshots__/Divider.test.tsx.snap +37 -0
- package/__tests__/__snapshots__/Input.test.tsx.snap +73 -0
- package/__tests__/__snapshots__/Stack.test.tsx.snap +101 -0
- package/__tests__/__snapshots__/Text.test.tsx.snap +39 -0
- package/package.json +47 -0
- package/src/Button/Button.tsx +119 -0
- package/src/Button/index.ts +2 -0
- package/src/Button/types.ts +43 -0
- package/src/Card/Card.tsx +62 -0
- package/src/Card/index.ts +2 -0
- package/src/Card/types.ts +21 -0
- package/src/Container/Container.tsx +42 -0
- package/src/Container/index.ts +2 -0
- package/src/Container/types.ts +23 -0
- package/src/Divider/Divider.tsx +40 -0
- package/src/Divider/index.ts +2 -0
- package/src/Divider/types.ts +21 -0
- package/src/Input/Input.tsx +103 -0
- package/src/Input/index.ts +2 -0
- package/src/Input/types.ts +23 -0
- package/src/Stack/Stack.tsx +77 -0
- package/src/Stack/index.ts +2 -0
- package/src/Stack/types.ts +30 -0
- package/src/Text/Text.tsx +50 -0
- package/src/Text/index.ts +2 -0
- package/src/Text/types.ts +21 -0
- package/src/index.ts +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# @astacinco/rn-primitives
|
|
2
|
+
|
|
3
|
+
Theme-aware UI primitives for React Native. All components automatically adapt to light/dark modes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @astacinco/rn-primitives @astacinco/rn-theming
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { ThemeProvider } from '@astacinco/rn-theming';
|
|
15
|
+
import { Text, Button, Card, VStack } from '@astacinco/rn-primitives';
|
|
16
|
+
|
|
17
|
+
function App() {
|
|
18
|
+
return (
|
|
19
|
+
<ThemeProvider mode="auto">
|
|
20
|
+
<VStack spacing="md">
|
|
21
|
+
<Text variant="title">Welcome</Text>
|
|
22
|
+
<Card>
|
|
23
|
+
<Text>This is a themed card</Text>
|
|
24
|
+
</Card>
|
|
25
|
+
<Button label="Get Started" onPress={() => {}} />
|
|
26
|
+
</VStack>
|
|
27
|
+
</ThemeProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Components
|
|
33
|
+
|
|
34
|
+
### Text
|
|
35
|
+
|
|
36
|
+
Themed text with variants.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
<Text variant="title">Title Text</Text>
|
|
40
|
+
<Text variant="subtitle">Subtitle Text</Text>
|
|
41
|
+
<Text variant="body">Body Text</Text>
|
|
42
|
+
<Text variant="caption">Caption Text</Text>
|
|
43
|
+
<Text variant="label">Label Text</Text>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Props:**
|
|
47
|
+
- `variant` - `'title' | 'subtitle' | 'body' | 'caption' | 'label'` (default: `'body'`)
|
|
48
|
+
- `color` - Override text color (uses theme colors by default)
|
|
49
|
+
- All standard `Text` props from React Native
|
|
50
|
+
|
|
51
|
+
### Button
|
|
52
|
+
|
|
53
|
+
Themed button with variants.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
<Button label="Primary" variant="primary" onPress={handlePress} />
|
|
57
|
+
<Button label="Secondary" variant="secondary" onPress={handlePress} />
|
|
58
|
+
<Button label="Outline" variant="outline" onPress={handlePress} />
|
|
59
|
+
<Button label="Ghost" variant="ghost" onPress={handlePress} />
|
|
60
|
+
<Button label="Disabled" disabled onPress={handlePress} />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Props:**
|
|
64
|
+
- `label` - Button text (required)
|
|
65
|
+
- `variant` - `'primary' | 'secondary' | 'outline' | 'ghost'` (default: `'primary'`)
|
|
66
|
+
- `size` - `'sm' | 'md' | 'lg'` (default: `'md'`)
|
|
67
|
+
- `disabled` - Disable the button
|
|
68
|
+
- `loading` - Show loading state
|
|
69
|
+
- `onPress` - Press handler (required)
|
|
70
|
+
|
|
71
|
+
### Card
|
|
72
|
+
|
|
73
|
+
Themed card container.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
<Card>
|
|
77
|
+
<Text>Card content</Text>
|
|
78
|
+
</Card>
|
|
79
|
+
|
|
80
|
+
<Card variant="outlined">
|
|
81
|
+
<Text>Outlined card</Text>
|
|
82
|
+
</Card>
|
|
83
|
+
|
|
84
|
+
<Card variant="elevated">
|
|
85
|
+
<Text>Elevated card with shadow</Text>
|
|
86
|
+
</Card>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Props:**
|
|
90
|
+
- `variant` - `'filled' | 'outlined' | 'elevated'` (default: `'filled'`)
|
|
91
|
+
- `padding` - Override padding
|
|
92
|
+
|
|
93
|
+
### Stack (VStack, HStack)
|
|
94
|
+
|
|
95
|
+
Layout components for vertical and horizontal stacking.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
<VStack spacing="md" align="center">
|
|
99
|
+
<Text>Item 1</Text>
|
|
100
|
+
<Text>Item 2</Text>
|
|
101
|
+
</VStack>
|
|
102
|
+
|
|
103
|
+
<HStack spacing="sm" justify="space-between">
|
|
104
|
+
<Button label="Cancel" variant="ghost" />
|
|
105
|
+
<Button label="Save" />
|
|
106
|
+
</HStack>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Props:**
|
|
110
|
+
- `spacing` - Gap between items: `'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'`
|
|
111
|
+
- `align` - Align items: `'start' | 'center' | 'end' | 'stretch'`
|
|
112
|
+
- `justify` - Justify content: `'start' | 'center' | 'end' | 'space-between' | 'space-around'`
|
|
113
|
+
|
|
114
|
+
### Container
|
|
115
|
+
|
|
116
|
+
Themed container with padding and optional max width.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
<Container>
|
|
120
|
+
<Text>Full width content</Text>
|
|
121
|
+
</Container>
|
|
122
|
+
|
|
123
|
+
<Container maxWidth={600} centered>
|
|
124
|
+
<Text>Centered content with max width</Text>
|
|
125
|
+
</Container>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Props:**
|
|
129
|
+
- `padding` - Override padding
|
|
130
|
+
- `maxWidth` - Maximum width in pixels
|
|
131
|
+
- `centered` - Center the container horizontally
|
|
132
|
+
|
|
133
|
+
### Input
|
|
134
|
+
|
|
135
|
+
Themed text input.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
<Input
|
|
139
|
+
label="Email"
|
|
140
|
+
placeholder="Enter your email"
|
|
141
|
+
value={email}
|
|
142
|
+
onChangeText={setEmail}
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
<Input
|
|
146
|
+
label="Password"
|
|
147
|
+
secureTextEntry
|
|
148
|
+
value={password}
|
|
149
|
+
onChangeText={setPassword}
|
|
150
|
+
error="Password is required"
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Props:**
|
|
155
|
+
- `label` - Input label
|
|
156
|
+
- `placeholder` - Placeholder text
|
|
157
|
+
- `error` - Error message to display
|
|
158
|
+
- `disabled` - Disable the input
|
|
159
|
+
- All standard `TextInput` props
|
|
160
|
+
|
|
161
|
+
### Divider
|
|
162
|
+
|
|
163
|
+
Themed horizontal divider.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
<Divider />
|
|
167
|
+
<Divider variant="thick" />
|
|
168
|
+
<Divider color="#FF0000" />
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Props:**
|
|
172
|
+
- `variant` - `'thin' | 'thick'` (default: `'thin'`)
|
|
173
|
+
- `color` - Override divider color
|
|
174
|
+
|
|
175
|
+
## Theme Integration
|
|
176
|
+
|
|
177
|
+
All components use the `useTheme()` hook from `@astacinco/rn-theming`. They automatically:
|
|
178
|
+
|
|
179
|
+
- Adapt colors to light/dark mode
|
|
180
|
+
- Use consistent spacing tokens
|
|
181
|
+
- Apply proper typography styles
|
|
182
|
+
- Support scoped theming
|
|
183
|
+
|
|
184
|
+
## Testing
|
|
185
|
+
|
|
186
|
+
Use `@astacinco/rn-testing` for testing primitives:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { renderWithTheme, createThemeSnapshot } from '@astacinco/rn-testing';
|
|
190
|
+
import { Button } from '@astacinco/rn-primitives';
|
|
191
|
+
|
|
192
|
+
describe('Button', () => {
|
|
193
|
+
createThemeSnapshot(<Button label="Click" onPress={() => {}} />);
|
|
194
|
+
});
|
|
195
|
+
```
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { renderWithTheme, createThemeSnapshot } from '@astacinco/rn-testing';
|
|
4
|
+
import { Button } from '../src/Button';
|
|
5
|
+
|
|
6
|
+
describe('Button', () => {
|
|
7
|
+
const mockOnPress = jest.fn();
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mockOnPress.mockClear();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Snapshot tests for both themes
|
|
14
|
+
createThemeSnapshot(<Button label="Click Me" onPress={() => {}} testID="button" />);
|
|
15
|
+
|
|
16
|
+
describe('variants', () => {
|
|
17
|
+
it('renders_primary_variant_byDefault', () => {
|
|
18
|
+
const { getByTestId } = renderWithTheme(
|
|
19
|
+
<Button label="Primary" onPress={mockOnPress} testID="button" />
|
|
20
|
+
);
|
|
21
|
+
expect(getByTestId('button')).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders_secondary_variant', () => {
|
|
25
|
+
const { getByTestId } = renderWithTheme(
|
|
26
|
+
<Button label="Secondary" variant="secondary" onPress={mockOnPress} testID="button" />
|
|
27
|
+
);
|
|
28
|
+
expect(getByTestId('button')).toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders_outline_variant', () => {
|
|
32
|
+
const { getByTestId } = renderWithTheme(
|
|
33
|
+
<Button label="Outline" variant="outline" onPress={mockOnPress} testID="button" />
|
|
34
|
+
);
|
|
35
|
+
expect(getByTestId('button')).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders_ghost_variant', () => {
|
|
39
|
+
const { getByTestId } = renderWithTheme(
|
|
40
|
+
<Button label="Ghost" variant="ghost" onPress={mockOnPress} testID="button" />
|
|
41
|
+
);
|
|
42
|
+
expect(getByTestId('button')).toBeTruthy();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('sizes', () => {
|
|
47
|
+
it('renders_sm_size', () => {
|
|
48
|
+
const { getByTestId } = renderWithTheme(
|
|
49
|
+
<Button label="Small" size="sm" onPress={mockOnPress} testID="button" />
|
|
50
|
+
);
|
|
51
|
+
expect(getByTestId('button')).toBeTruthy();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders_md_size_byDefault', () => {
|
|
55
|
+
const { getByTestId } = renderWithTheme(
|
|
56
|
+
<Button label="Medium" onPress={mockOnPress} testID="button" />
|
|
57
|
+
);
|
|
58
|
+
expect(getByTestId('button')).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('renders_lg_size', () => {
|
|
62
|
+
const { getByTestId } = renderWithTheme(
|
|
63
|
+
<Button label="Large" size="lg" onPress={mockOnPress} testID="button" />
|
|
64
|
+
);
|
|
65
|
+
expect(getByTestId('button')).toBeTruthy();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('interactions', () => {
|
|
70
|
+
it('calls_onPress_whenPressed', () => {
|
|
71
|
+
const { getByTestId } = renderWithTheme(
|
|
72
|
+
<Button label="Press Me" onPress={mockOnPress} testID="button" />
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
fireEvent.press(getByTestId('button'));
|
|
76
|
+
expect(mockOnPress).toHaveBeenCalledTimes(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('does_notCall_onPress_whenDisabled', () => {
|
|
80
|
+
const { getByTestId } = renderWithTheme(
|
|
81
|
+
<Button label="Disabled" disabled onPress={mockOnPress} testID="button" />
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
fireEvent.press(getByTestId('button'));
|
|
85
|
+
expect(mockOnPress).not.toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does_notCall_onPress_whenLoading', () => {
|
|
89
|
+
const { getByTestId } = renderWithTheme(
|
|
90
|
+
<Button label="Loading" loading onPress={mockOnPress} testID="button" />
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
fireEvent.press(getByTestId('button'));
|
|
94
|
+
expect(mockOnPress).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('theming', () => {
|
|
99
|
+
it('uses_different_colors_inDarkMode', () => {
|
|
100
|
+
const lightResult = renderWithTheme(
|
|
101
|
+
<Button label="Test" onPress={mockOnPress} testID="button" />,
|
|
102
|
+
'light'
|
|
103
|
+
);
|
|
104
|
+
const darkResult = renderWithTheme(
|
|
105
|
+
<Button label="Test" onPress={mockOnPress} testID="button" />,
|
|
106
|
+
'dark'
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Both should render without errors
|
|
110
|
+
expect(lightResult.getByTestId('button')).toBeTruthy();
|
|
111
|
+
expect(darkResult.getByTestId('button')).toBeTruthy();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { renderWithTheme, createThemeSnapshot } from '@astacinco/rn-testing';
|
|
4
|
+
import { Card } from '../src/Card';
|
|
5
|
+
|
|
6
|
+
describe('Card', () => {
|
|
7
|
+
// Snapshot tests for both themes
|
|
8
|
+
createThemeSnapshot(
|
|
9
|
+
<Card testID="card">
|
|
10
|
+
<Text>Card content</Text>
|
|
11
|
+
</Card>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
describe('variants', () => {
|
|
15
|
+
it('renders_filled_variant_byDefault', () => {
|
|
16
|
+
const { getByTestId } = renderWithTheme(
|
|
17
|
+
<Card testID="card">
|
|
18
|
+
<Text>Content</Text>
|
|
19
|
+
</Card>
|
|
20
|
+
);
|
|
21
|
+
expect(getByTestId('card')).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders_outlined_variant', () => {
|
|
25
|
+
const { getByTestId } = renderWithTheme(
|
|
26
|
+
<Card variant="outlined" testID="card">
|
|
27
|
+
<Text>Content</Text>
|
|
28
|
+
</Card>
|
|
29
|
+
);
|
|
30
|
+
expect(getByTestId('card')).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders_elevated_variant', () => {
|
|
34
|
+
const { getByTestId } = renderWithTheme(
|
|
35
|
+
<Card variant="elevated" testID="card">
|
|
36
|
+
<Text>Content</Text>
|
|
37
|
+
</Card>
|
|
38
|
+
);
|
|
39
|
+
expect(getByTestId('card')).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('theming', () => {
|
|
44
|
+
it('uses_different_colors_inDarkMode', () => {
|
|
45
|
+
const lightResult = renderWithTheme(
|
|
46
|
+
<Card testID="card">
|
|
47
|
+
<Text>Test</Text>
|
|
48
|
+
</Card>,
|
|
49
|
+
'light'
|
|
50
|
+
);
|
|
51
|
+
const darkResult = renderWithTheme(
|
|
52
|
+
<Card testID="card">
|
|
53
|
+
<Text>Test</Text>
|
|
54
|
+
</Card>,
|
|
55
|
+
'dark'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const lightBg = lightResult.getByTestId('card').props.style[1].backgroundColor;
|
|
59
|
+
const darkBg = darkResult.getByTestId('card').props.style[1].backgroundColor;
|
|
60
|
+
|
|
61
|
+
expect(lightBg).not.toBe(darkBg);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('accepts_custom_padding', () => {
|
|
66
|
+
const { getByTestId } = renderWithTheme(
|
|
67
|
+
<Card padding={32} testID="card">
|
|
68
|
+
<Text>Content</Text>
|
|
69
|
+
</Card>
|
|
70
|
+
);
|
|
71
|
+
expect(getByTestId('card').props.style[1].padding).toBe(32);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { renderWithTheme, createThemeSnapshot } from '@astacinco/rn-testing';
|
|
4
|
+
import { Container } from '../src/Container';
|
|
5
|
+
|
|
6
|
+
describe('Container', () => {
|
|
7
|
+
// Snapshot tests for both themes
|
|
8
|
+
createThemeSnapshot(
|
|
9
|
+
<Container testID="container">
|
|
10
|
+
<Text>Content</Text>
|
|
11
|
+
</Container>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
it('renders_children', () => {
|
|
15
|
+
const { getByText } = renderWithTheme(
|
|
16
|
+
<Container testID="container">
|
|
17
|
+
<Text>Hello</Text>
|
|
18
|
+
</Container>
|
|
19
|
+
);
|
|
20
|
+
expect(getByText('Hello')).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('applies_maxWidth', () => {
|
|
24
|
+
const { getByTestId } = renderWithTheme(
|
|
25
|
+
<Container maxWidth={600} testID="container">
|
|
26
|
+
<Text>Content</Text>
|
|
27
|
+
</Container>
|
|
28
|
+
);
|
|
29
|
+
expect(getByTestId('container').props.style[1].maxWidth).toBe(600);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('applies_custom_padding', () => {
|
|
33
|
+
const { getByTestId } = renderWithTheme(
|
|
34
|
+
<Container padding={32} testID="container">
|
|
35
|
+
<Text>Content</Text>
|
|
36
|
+
</Container>
|
|
37
|
+
);
|
|
38
|
+
expect(getByTestId('container').props.style[1].padding).toBe(32);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('centers_whenCenteredProp', () => {
|
|
42
|
+
const { getByTestId } = renderWithTheme(
|
|
43
|
+
<Container centered testID="container">
|
|
44
|
+
<Text>Content</Text>
|
|
45
|
+
</Container>
|
|
46
|
+
);
|
|
47
|
+
expect(getByTestId('container').props.style[1].alignSelf).toBe('center');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('theming', () => {
|
|
51
|
+
it('uses_different_colors_inDarkMode', () => {
|
|
52
|
+
const lightResult = renderWithTheme(
|
|
53
|
+
<Container testID="container">
|
|
54
|
+
<Text>Test</Text>
|
|
55
|
+
</Container>,
|
|
56
|
+
'light'
|
|
57
|
+
);
|
|
58
|
+
const darkResult = renderWithTheme(
|
|
59
|
+
<Container testID="container">
|
|
60
|
+
<Text>Test</Text>
|
|
61
|
+
</Container>,
|
|
62
|
+
'dark'
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const lightBg = lightResult.getByTestId('container').props.style[1].backgroundColor;
|
|
66
|
+
const darkBg = darkResult.getByTestId('container').props.style[1].backgroundColor;
|
|
67
|
+
|
|
68
|
+
expect(lightBg).not.toBe(darkBg);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderWithTheme, createThemeSnapshot } from '@astacinco/rn-testing';
|
|
3
|
+
import { Divider } from '../src/Divider';
|
|
4
|
+
|
|
5
|
+
describe('Divider', () => {
|
|
6
|
+
// Snapshot tests for both themes
|
|
7
|
+
createThemeSnapshot(<Divider testID="divider" />);
|
|
8
|
+
|
|
9
|
+
describe('variants', () => {
|
|
10
|
+
it('renders_thin_variant_byDefault', () => {
|
|
11
|
+
const { getByTestId } = renderWithTheme(<Divider testID="divider" />);
|
|
12
|
+
expect(getByTestId('divider').props.style[1].height).toBe(1);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders_thick_variant', () => {
|
|
16
|
+
const { getByTestId } = renderWithTheme(
|
|
17
|
+
<Divider variant="thick" testID="divider" />
|
|
18
|
+
);
|
|
19
|
+
expect(getByTestId('divider').props.style[1].height).toBe(2);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('theming', () => {
|
|
24
|
+
it('uses_different_colors_inDarkMode', () => {
|
|
25
|
+
const lightResult = renderWithTheme(<Divider testID="divider" />, 'light');
|
|
26
|
+
const darkResult = renderWithTheme(<Divider testID="divider" />, 'dark');
|
|
27
|
+
|
|
28
|
+
const lightColor = lightResult.getByTestId('divider').props.style[1].backgroundColor;
|
|
29
|
+
const darkColor = darkResult.getByTestId('divider').props.style[1].backgroundColor;
|
|
30
|
+
|
|
31
|
+
expect(lightColor).not.toBe(darkColor);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('accepts_custom_color', () => {
|
|
36
|
+
const { getByTestId } = renderWithTheme(
|
|
37
|
+
<Divider color="#FF0000" testID="divider" />
|
|
38
|
+
);
|
|
39
|
+
expect(getByTestId('divider').props.style[1].backgroundColor).toBe('#FF0000');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { renderWithTheme, createThemeSnapshot } from '@astacinco/rn-testing';
|
|
4
|
+
import { Input } from '../src/Input';
|
|
5
|
+
|
|
6
|
+
describe('Input', () => {
|
|
7
|
+
// Snapshot tests for both themes
|
|
8
|
+
createThemeSnapshot(
|
|
9
|
+
<Input testID="input" placeholder="Enter text" />
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
it('renders_withLabel', () => {
|
|
13
|
+
const { getByText } = renderWithTheme(
|
|
14
|
+
<Input label="Email" testID="input" />
|
|
15
|
+
);
|
|
16
|
+
expect(getByText('Email')).toBeTruthy();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders_withError', () => {
|
|
20
|
+
const { getByText } = renderWithTheme(
|
|
21
|
+
<Input error="This field is required" testID="input" />
|
|
22
|
+
);
|
|
23
|
+
expect(getByText('This field is required')).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders_withPlaceholder', () => {
|
|
27
|
+
const { getByTestId } = renderWithTheme(
|
|
28
|
+
<Input placeholder="Enter email" testID="input" />
|
|
29
|
+
);
|
|
30
|
+
expect(getByTestId('input').props.placeholder).toBe('Enter email');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('interactions', () => {
|
|
34
|
+
it('calls_onChangeText_whenTyping', () => {
|
|
35
|
+
const mockOnChangeText = jest.fn();
|
|
36
|
+
const { getByTestId } = renderWithTheme(
|
|
37
|
+
<Input onChangeText={mockOnChangeText} testID="input" />
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
fireEvent.changeText(getByTestId('input'), 'hello');
|
|
41
|
+
expect(mockOnChangeText).toHaveBeenCalledWith('hello');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('is_notEditable_whenDisabled', () => {
|
|
45
|
+
const { getByTestId } = renderWithTheme(
|
|
46
|
+
<Input disabled testID="input" />
|
|
47
|
+
);
|
|
48
|
+
expect(getByTestId('input').props.editable).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('theming', () => {
|
|
53
|
+
it('uses_different_colors_inDarkMode', () => {
|
|
54
|
+
const lightResult = renderWithTheme(
|
|
55
|
+
<Input testID="input" />,
|
|
56
|
+
'light'
|
|
57
|
+
);
|
|
58
|
+
const darkResult = renderWithTheme(
|
|
59
|
+
<Input testID="input" />,
|
|
60
|
+
'dark'
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const lightBorder = lightResult.getByTestId('input').props.style[1].borderColor;
|
|
64
|
+
const darkBorder = darkResult.getByTestId('input').props.style[1].borderColor;
|
|
65
|
+
|
|
66
|
+
expect(lightBorder).not.toBe(darkBorder);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { renderWithTheme, createThemeSnapshot } from '@astacinco/rn-testing';
|
|
4
|
+
import { VStack, HStack } from '../src/Stack';
|
|
5
|
+
|
|
6
|
+
describe('VStack', () => {
|
|
7
|
+
// Snapshot tests for both themes
|
|
8
|
+
createThemeSnapshot(
|
|
9
|
+
<VStack testID="vstack" spacing="md">
|
|
10
|
+
<Text>Item 1</Text>
|
|
11
|
+
<Text>Item 2</Text>
|
|
12
|
+
</VStack>,
|
|
13
|
+
'VStack'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
it('renders_children_vertically', () => {
|
|
17
|
+
const { getByTestId } = renderWithTheme(
|
|
18
|
+
<VStack testID="vstack">
|
|
19
|
+
<Text>Item 1</Text>
|
|
20
|
+
<Text>Item 2</Text>
|
|
21
|
+
</VStack>
|
|
22
|
+
);
|
|
23
|
+
expect(getByTestId('vstack').props.style[1].flexDirection).toBe('column');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('applies_spacing', () => {
|
|
27
|
+
const { getByTestId } = renderWithTheme(
|
|
28
|
+
<VStack testID="vstack" spacing="md">
|
|
29
|
+
<Text>Item 1</Text>
|
|
30
|
+
<Text>Item 2</Text>
|
|
31
|
+
</VStack>
|
|
32
|
+
);
|
|
33
|
+
expect(getByTestId('vstack').props.style[1].gap).toBe(16); // md = 16
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('applies_alignment', () => {
|
|
37
|
+
const { getByTestId } = renderWithTheme(
|
|
38
|
+
<VStack testID="vstack" align="center">
|
|
39
|
+
<Text>Item</Text>
|
|
40
|
+
</VStack>
|
|
41
|
+
);
|
|
42
|
+
expect(getByTestId('vstack').props.style[1].alignItems).toBe('center');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('applies_justification', () => {
|
|
46
|
+
const { getByTestId } = renderWithTheme(
|
|
47
|
+
<VStack testID="vstack" justify="space-between">
|
|
48
|
+
<Text>Item 1</Text>
|
|
49
|
+
<Text>Item 2</Text>
|
|
50
|
+
</VStack>
|
|
51
|
+
);
|
|
52
|
+
expect(getByTestId('vstack').props.style[1].justifyContent).toBe('space-between');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('HStack', () => {
|
|
57
|
+
// Snapshot tests for both themes
|
|
58
|
+
createThemeSnapshot(
|
|
59
|
+
<HStack testID="hstack" spacing="sm">
|
|
60
|
+
<Text>Left</Text>
|
|
61
|
+
<Text>Right</Text>
|
|
62
|
+
</HStack>,
|
|
63
|
+
'HStack'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
it('renders_children_horizontally', () => {
|
|
67
|
+
const { getByTestId } = renderWithTheme(
|
|
68
|
+
<HStack testID="hstack">
|
|
69
|
+
<Text>Left</Text>
|
|
70
|
+
<Text>Right</Text>
|
|
71
|
+
</HStack>
|
|
72
|
+
);
|
|
73
|
+
expect(getByTestId('hstack').props.style[1].flexDirection).toBe('row');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('applies_spacing', () => {
|
|
77
|
+
const { getByTestId } = renderWithTheme(
|
|
78
|
+
<HStack testID="hstack" spacing="lg">
|
|
79
|
+
<Text>Left</Text>
|
|
80
|
+
<Text>Right</Text>
|
|
81
|
+
</HStack>
|
|
82
|
+
);
|
|
83
|
+
expect(getByTestId('hstack').props.style[1].gap).toBe(24); // lg = 24
|
|
84
|
+
});
|
|
85
|
+
});
|