@sudobility/components-rn 1.0.22 → 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/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 +19 -8
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { ViewProps } from 'react-native';
|
|
3
3
|
export type ChainType = 'evm' | 'solana' | 'bitcoin' | 'cosmos';
|
|
4
|
+
/** Badge displaying a blockchain network identifier with chain-specific color and icon */
|
|
4
5
|
export interface ChainBadgeProps extends ViewProps {
|
|
5
6
|
chainType: ChainType;
|
|
6
7
|
size?: 'sm' | 'md' | 'lg';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChainBadge.d.ts","sourceRoot":"","sources":["../../../src/ui/ChainBadge/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,0FAA0F;AAC1F,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ChainBadge/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/components-rn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "React Native UI components and design system - Ported from @sudobility/components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -61,7 +61,16 @@
|
|
|
61
61
|
"clsx": "^2.0.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
+
"@babel/core": "^7.29.0",
|
|
65
|
+
"@babel/preset-env": "^7.29.0",
|
|
66
|
+
"@babel/preset-flow": "^7.27.1",
|
|
67
|
+
"@babel/preset-react": "^7.28.5",
|
|
68
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
69
|
+
"@babel/runtime": "^7.28.6",
|
|
64
70
|
"@eslint/js": "^9.15.0",
|
|
71
|
+
"@react-native/babel-preset": "^0.84.0",
|
|
72
|
+
"@react-native/js-polyfills": "^0.84.0",
|
|
73
|
+
"@react-native/normalize-colors": "^0.84.0",
|
|
65
74
|
"@sudobility/design": "^1.1.20",
|
|
66
75
|
"@sudobility/types": "^1.9.54",
|
|
67
76
|
"@testing-library/jest-native": "^5.4.3",
|
|
@@ -72,22 +81,24 @@
|
|
|
72
81
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
|
73
82
|
"@typescript-eslint/parser": "^8.15.0",
|
|
74
83
|
"ajv": "^8.17.1",
|
|
84
|
+
"babel-jest": "^30.2.0",
|
|
75
85
|
"class-variance-authority": "^0.7.1",
|
|
76
86
|
"clsx": "^2.1.1",
|
|
77
|
-
"eslint": "^9.
|
|
78
|
-
"eslint-config-prettier": "^
|
|
79
|
-
"eslint-plugin-prettier": "^5.
|
|
87
|
+
"eslint": "^9.38.0",
|
|
88
|
+
"eslint-config-prettier": "^10.1.8",
|
|
89
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
80
90
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
81
91
|
"eslint-plugin-react-refresh": "^0.4.14",
|
|
82
92
|
"jest": "^29.7.0",
|
|
83
93
|
"jest-environment-jsdom": "^30.2.0",
|
|
84
94
|
"nativewind": "^4.1.0",
|
|
85
|
-
"prettier": "^3.
|
|
95
|
+
"prettier": "^3.6.2",
|
|
86
96
|
"react": "^18.3.0",
|
|
87
97
|
"react-native": "^0.76.0",
|
|
88
|
-
"
|
|
98
|
+
"react-test-renderer": "18.3.1",
|
|
99
|
+
"typescript": "^5.9.3",
|
|
89
100
|
"vite": "^6.0.0",
|
|
90
|
-
"vite-plugin-dts": "^4.
|
|
101
|
+
"vite-plugin-dts": "^4.5.4"
|
|
91
102
|
},
|
|
92
103
|
"repository": {
|
|
93
104
|
"type": "git",
|
|
@@ -98,7 +109,7 @@
|
|
|
98
109
|
},
|
|
99
110
|
"homepage": "https://github.com/sudobility/components-rn#readme",
|
|
100
111
|
"publishConfig": {
|
|
101
|
-
"access": "
|
|
112
|
+
"access": "public"
|
|
102
113
|
},
|
|
103
114
|
"engines": {
|
|
104
115
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Alert, AlertTitle, AlertDescription } from '../ui/Alert';
|
|
5
|
+
|
|
6
|
+
// Mock @sudobility/design
|
|
7
|
+
jest.mock('@sudobility/design', () => ({
|
|
8
|
+
variants: {
|
|
9
|
+
alert: {
|
|
10
|
+
info: () => 'mocked-alert-info',
|
|
11
|
+
success: () => 'mocked-alert-success',
|
|
12
|
+
warning: () => 'mocked-alert-warning',
|
|
13
|
+
attention: () => 'mocked-alert-attention',
|
|
14
|
+
error: () => 'mocked-alert-error',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe('Alert', () => {
|
|
20
|
+
it('renders with title', () => {
|
|
21
|
+
render(<Alert title='Alert Title' />);
|
|
22
|
+
expect(screen.getByText('Alert Title')).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders with description', () => {
|
|
26
|
+
render(<Alert description='Alert description' />);
|
|
27
|
+
expect(screen.getByText('Alert description')).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders with both title and description', () => {
|
|
31
|
+
render(
|
|
32
|
+
<Alert title='Important' description='This is an important message' />
|
|
33
|
+
);
|
|
34
|
+
expect(screen.getByText('Important')).toBeTruthy();
|
|
35
|
+
expect(screen.getByText('This is an important message')).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('has alert accessibility role', () => {
|
|
39
|
+
render(<Alert title='Alert' testID='alert-el' />);
|
|
40
|
+
const alert = screen.getByTestId('alert-el');
|
|
41
|
+
expect(alert.props.accessibilityRole).toBe('alert');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders with different variants without crashing', () => {
|
|
45
|
+
const variants = [
|
|
46
|
+
'info',
|
|
47
|
+
'success',
|
|
48
|
+
'warning',
|
|
49
|
+
'attention',
|
|
50
|
+
'error',
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
variants.forEach(variant => {
|
|
54
|
+
const { unmount } = render(<Alert variant={variant} title={variant} />);
|
|
55
|
+
expect(screen.getByText(variant)).toBeTruthy();
|
|
56
|
+
unmount();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('renders default icons for each variant', () => {
|
|
61
|
+
render(<Alert variant='success' title='Success' testID='alert-icon' />);
|
|
62
|
+
const alert = screen.getByTestId('alert-icon');
|
|
63
|
+
expect(alert.props.accessibilityRole).toBe('alert');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('renders custom icon when provided', () => {
|
|
67
|
+
render(
|
|
68
|
+
<Alert icon={<Text testID='custom-icon'>*</Text>} title='Custom Icon' />
|
|
69
|
+
);
|
|
70
|
+
expect(screen.getByTestId('custom-icon')).toBeTruthy();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('renders children content', () => {
|
|
74
|
+
render(
|
|
75
|
+
<Alert>
|
|
76
|
+
<Text>Custom child content</Text>
|
|
77
|
+
</Alert>
|
|
78
|
+
);
|
|
79
|
+
expect(screen.getByText('Custom child content')).toBeTruthy();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('AlertTitle', () => {
|
|
84
|
+
it('renders text content', () => {
|
|
85
|
+
render(<AlertTitle>Title Text</AlertTitle>);
|
|
86
|
+
expect(screen.getByText('Title Text')).toBeTruthy();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('AlertDescription', () => {
|
|
91
|
+
it('renders text content', () => {
|
|
92
|
+
render(<AlertDescription>Description text</AlertDescription>);
|
|
93
|
+
expect(screen.getByText('Description text')).toBeTruthy();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Badge } from '../ui/Badge';
|
|
5
|
+
|
|
6
|
+
describe('Badge', () => {
|
|
7
|
+
it('renders children text', () => {
|
|
8
|
+
render(<Badge>Active</Badge>);
|
|
9
|
+
expect(screen.getByText('Active')).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders with different variants without crashing', () => {
|
|
13
|
+
const variants = [
|
|
14
|
+
'default',
|
|
15
|
+
'primary',
|
|
16
|
+
'success',
|
|
17
|
+
'warning',
|
|
18
|
+
'danger',
|
|
19
|
+
'info',
|
|
20
|
+
'purple',
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
variants.forEach(variant => {
|
|
24
|
+
const { unmount } = render(<Badge variant={variant}>{variant}</Badge>);
|
|
25
|
+
expect(screen.getByText(variant)).toBeTruthy();
|
|
26
|
+
unmount();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders with different sizes without crashing', () => {
|
|
31
|
+
const sizes = ['sm', 'md', 'lg'] as const;
|
|
32
|
+
|
|
33
|
+
sizes.forEach(size => {
|
|
34
|
+
const { unmount } = render(<Badge size={size}>Size {size}</Badge>);
|
|
35
|
+
expect(screen.getByText(`Size ${size}`)).toBeTruthy();
|
|
36
|
+
unmount();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('renders an icon when provided', () => {
|
|
41
|
+
render(
|
|
42
|
+
<Badge icon={<Text testID='badge-icon'>Star</Text>}>Featured</Badge>
|
|
43
|
+
);
|
|
44
|
+
expect(screen.getByTestId('badge-icon')).toBeTruthy();
|
|
45
|
+
expect(screen.getByText('Featured')).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('renders a dot indicator when dot is true', () => {
|
|
49
|
+
const { toJSON } = render(<Badge dot>Pending</Badge>);
|
|
50
|
+
expect(screen.getByText('Pending')).toBeTruthy();
|
|
51
|
+
// The dot is a View element that gets rendered in the tree
|
|
52
|
+
expect(toJSON()).toBeTruthy();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders as a pressable when onPress is provided', () => {
|
|
56
|
+
const onPress = jest.fn();
|
|
57
|
+
render(<Badge onPress={onPress}>Clickable</Badge>);
|
|
58
|
+
fireEvent.press(screen.getByRole('button'));
|
|
59
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does not have button role when onPress is not provided', () => {
|
|
63
|
+
render(<Badge>Static</Badge>);
|
|
64
|
+
expect(screen.queryByRole('button')).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('renders dismiss button when dismissible', () => {
|
|
68
|
+
const onDismiss = jest.fn();
|
|
69
|
+
render(
|
|
70
|
+
<Badge dismissible onDismiss={onDismiss}>
|
|
71
|
+
Dismissable
|
|
72
|
+
</Badge>
|
|
73
|
+
);
|
|
74
|
+
const dismissButton = screen.getByLabelText('Dismiss');
|
|
75
|
+
expect(dismissButton).toBeTruthy();
|
|
76
|
+
fireEvent.press(dismissButton);
|
|
77
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('does not render dismiss button when not dismissible', () => {
|
|
81
|
+
render(<Badge>Not dismissable</Badge>);
|
|
82
|
+
expect(screen.queryByLabelText('Dismiss')).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('displays count', () => {
|
|
86
|
+
render(<Badge count={5}>Items</Badge>);
|
|
87
|
+
expect(screen.getByText('5')).toBeTruthy();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('displays maxCount with + when count exceeds it', () => {
|
|
91
|
+
render(
|
|
92
|
+
<Badge count={150} maxCount={99}>
|
|
93
|
+
Items
|
|
94
|
+
</Badge>
|
|
95
|
+
);
|
|
96
|
+
expect(screen.getByText('99+')).toBeTruthy();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('displays exact count when within maxCount', () => {
|
|
100
|
+
render(
|
|
101
|
+
<Badge count={50} maxCount={99}>
|
|
102
|
+
Items
|
|
103
|
+
</Badge>
|
|
104
|
+
);
|
|
105
|
+
expect(screen.getByText('50')).toBeTruthy();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('renders with outline style without crashing', () => {
|
|
109
|
+
render(
|
|
110
|
+
<Badge variant='primary' outline>
|
|
111
|
+
Outlined
|
|
112
|
+
</Badge>
|
|
113
|
+
);
|
|
114
|
+
expect(screen.getByText('Outlined')).toBeTruthy();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('renders with pill shape without crashing', () => {
|
|
118
|
+
render(<Badge pill>Pill</Badge>);
|
|
119
|
+
expect(screen.getByText('Pill')).toBeTruthy();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { Button } from '../ui/Button';
|
|
5
|
+
|
|
6
|
+
// Mock @sudobility/design
|
|
7
|
+
jest.mock('@sudobility/design', () => ({
|
|
8
|
+
variants: {
|
|
9
|
+
button: new Proxy(
|
|
10
|
+
{},
|
|
11
|
+
{
|
|
12
|
+
get: () =>
|
|
13
|
+
new Proxy(() => 'mocked-class', {
|
|
14
|
+
get: () => () => 'mocked-class',
|
|
15
|
+
}),
|
|
16
|
+
}
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe('Button', () => {
|
|
22
|
+
it('renders with string children', () => {
|
|
23
|
+
render(<Button>Press me</Button>);
|
|
24
|
+
expect(screen.getByText('Press me')).toBeTruthy();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders with custom children (non-string)', () => {
|
|
28
|
+
render(
|
|
29
|
+
<Button>
|
|
30
|
+
<Text testID='custom-child'>Custom</Text>
|
|
31
|
+
</Button>
|
|
32
|
+
);
|
|
33
|
+
expect(screen.getByTestId('custom-child')).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('has button accessibility role', () => {
|
|
37
|
+
render(<Button>Click</Button>);
|
|
38
|
+
expect(screen.getByRole('button')).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('calls onPress when pressed', () => {
|
|
42
|
+
const onPress = jest.fn();
|
|
43
|
+
render(<Button onPress={onPress}>Click</Button>);
|
|
44
|
+
fireEvent.press(screen.getByRole('button'));
|
|
45
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('does not call onPress when disabled', () => {
|
|
49
|
+
const onPress = jest.fn();
|
|
50
|
+
render(
|
|
51
|
+
<Button onPress={onPress} disabled>
|
|
52
|
+
Click
|
|
53
|
+
</Button>
|
|
54
|
+
);
|
|
55
|
+
fireEvent.press(screen.getByRole('button'));
|
|
56
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('shows spinner when loading', () => {
|
|
60
|
+
render(<Button loading>Loading</Button>);
|
|
61
|
+
// ActivityIndicator is rendered when loading is true
|
|
62
|
+
// The button should also be disabled
|
|
63
|
+
const button = screen.getByRole('button');
|
|
64
|
+
expect(button.props.accessibilityState).toEqual({ disabled: true });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('applies accessibility label', () => {
|
|
68
|
+
render(<Button accessibilityLabel='Submit form'>Submit</Button>);
|
|
69
|
+
const button = screen.getByRole('button');
|
|
70
|
+
expect(button.props.accessibilityLabel).toBe('Submit form');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('sets disabled accessibility state when disabled', () => {
|
|
74
|
+
render(<Button disabled>Disabled</Button>);
|
|
75
|
+
const button = screen.getByRole('button');
|
|
76
|
+
expect(button.props.accessibilityState).toEqual({ disabled: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('renders with different variants without crashing', () => {
|
|
80
|
+
const variants = [
|
|
81
|
+
'default',
|
|
82
|
+
'primary',
|
|
83
|
+
'secondary',
|
|
84
|
+
'outline',
|
|
85
|
+
'ghost',
|
|
86
|
+
'destructive',
|
|
87
|
+
'success',
|
|
88
|
+
'link',
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
variants.forEach(variant => {
|
|
92
|
+
const { unmount } = render(<Button variant={variant}>{variant}</Button>);
|
|
93
|
+
expect(screen.getByText(variant)).toBeTruthy();
|
|
94
|
+
unmount();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders with different sizes without crashing', () => {
|
|
99
|
+
const sizes = ['default', 'sm', 'lg', 'icon'] as const;
|
|
100
|
+
|
|
101
|
+
sizes.forEach(size => {
|
|
102
|
+
const { unmount } = render(<Button size={size}>{`Size ${size}`}</Button>);
|
|
103
|
+
expect(screen.getByText(`Size ${size}`)).toBeTruthy();
|
|
104
|
+
unmount();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -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
|
+
});
|