@idealyst/cli 1.0.28 → 1.0.29
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/package.json +1 -1
- package/templates/api/__tests__/api.test.ts +26 -0
- package/templates/api/jest.config.js +23 -0
- package/templates/api/jest.setup.js +9 -0
- package/templates/api/package.json +6 -0
- package/templates/native/__tests__/App.test.tsx +156 -0
- package/templates/native/__tests__/components.test.tsx +300 -0
- package/templates/native/jest.config.js +21 -0
- package/templates/native/jest.setup.js +12 -0
- package/templates/native/package.json +34 -0
- package/templates/shared/__tests__/shared.test.ts +39 -0
- package/templates/shared/jest.config.js +22 -0
- package/templates/shared/package.json +10 -0
- package/templates/web/__tests__/App.test.tsx +342 -0
- package/templates/web/__tests__/components.test.tsx +564 -0
- package/templates/web/jest.config.js +27 -0
- package/templates/web/jest.setup.js +24 -0
- package/templates/web/package.json +11 -1
- package/templates/workspace/.devcontainer/devcontainer.json +150 -0
- package/templates/workspace/.devcontainer/post-create.sh +129 -0
- package/templates/workspace/.dockerignore +151 -0
- package/templates/workspace/.env.example +36 -0
- package/templates/workspace/.env.production +56 -0
- package/templates/workspace/DOCKER.md +385 -0
- package/templates/workspace/Dockerfile +100 -0
- package/templates/workspace/README.md +93 -0
- package/templates/workspace/docker/nginx/prod.conf +238 -0
- package/templates/workspace/docker/nginx.conf +131 -0
- package/templates/workspace/docker/postgres/init.sql +41 -0
- package/templates/workspace/docker/prometheus/prometheus.yml +52 -0
- package/templates/workspace/docker-compose.prod.yml +146 -0
- package/templates/workspace/docker-compose.yml +144 -0
- package/templates/workspace/jest.config.js +20 -0
- package/templates/workspace/package.json +11 -1
- package/templates/workspace/scripts/docker/db-backup.sh +230 -0
- package/templates/workspace/scripts/docker/deploy.sh +212 -0
- package/templates/workspace/scripts/test-runner.js +120 -0
- package/templates/workspace/setup.sh +205 -0
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { appRouter } from '../src/router/index.js';
|
|
3
|
+
|
|
4
|
+
describe('API Router', () => {
|
|
5
|
+
it('should have a valid router configuration', () => {
|
|
6
|
+
expect(appRouter).toBeDefined();
|
|
7
|
+
expect(typeof appRouter).toBe('object');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should export the expected router structure', () => {
|
|
11
|
+
// Test that the router has the expected structure
|
|
12
|
+
expect(appRouter._def).toBeDefined();
|
|
13
|
+
expect(appRouter._def.router).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Sample API Test', () => {
|
|
18
|
+
it('should pass a basic test', () => {
|
|
19
|
+
expect(1 + 1).toBe(2);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle async operations', async () => {
|
|
23
|
+
const result = await Promise.resolve('test');
|
|
24
|
+
expect(result).toBe('test');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
roots: ['<rootDir>/src', '<rootDir>/__tests__'],
|
|
6
|
+
testMatch: [
|
|
7
|
+
'**/__tests__/**/*.{ts,tsx,js}',
|
|
8
|
+
'**/*.{test,spec}.{ts,tsx,js}'
|
|
9
|
+
],
|
|
10
|
+
transform: {
|
|
11
|
+
'^.+\\.tsx?$': 'ts-jest',
|
|
12
|
+
},
|
|
13
|
+
collectCoverageFrom: [
|
|
14
|
+
'src/**/*.{ts,tsx}',
|
|
15
|
+
'!src/**/*.d.ts',
|
|
16
|
+
'!src/**/index.ts',
|
|
17
|
+
],
|
|
18
|
+
coverageDirectory: 'coverage',
|
|
19
|
+
coverageReporters: ['text', 'lcov'],
|
|
20
|
+
testTimeout: 10000,
|
|
21
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
|
22
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
23
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Global test setup for API tests
|
|
2
|
+
// This file runs before all tests
|
|
3
|
+
|
|
4
|
+
// Mock environment variables for testing
|
|
5
|
+
process.env.NODE_ENV = 'test';
|
|
6
|
+
process.env.DATABASE_URL = 'file:./test.db';
|
|
7
|
+
|
|
8
|
+
// Increase timeout for async operations
|
|
9
|
+
jest.setTimeout(10000);
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"dev": "tsx watch src/server.ts",
|
|
17
17
|
"start": "node dist/server.js",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch",
|
|
20
|
+
"test:coverage": "jest --coverage",
|
|
18
21
|
"db:generate": "prisma generate",
|
|
19
22
|
"db:push": "prisma db push",
|
|
20
23
|
"db:studio": "prisma studio",
|
|
@@ -35,11 +38,14 @@
|
|
|
35
38
|
"devDependencies": {
|
|
36
39
|
"@types/cors": "^2.8.17",
|
|
37
40
|
"@types/express": "^4.17.21",
|
|
41
|
+
"@types/jest": "^29.5.12",
|
|
38
42
|
"@types/node": "^20.10.4",
|
|
39
43
|
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
|
40
44
|
"@typescript-eslint/parser": "^6.13.1",
|
|
41
45
|
"eslint": "^8.54.0",
|
|
46
|
+
"jest": "^29.7.0",
|
|
42
47
|
"prisma": "^5.7.1",
|
|
48
|
+
"ts-jest": "^29.1.2",
|
|
43
49
|
"tsx": "^4.6.2",
|
|
44
50
|
"typescript": "^5.3.3"
|
|
45
51
|
},
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @format
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import ReactTestRenderer from 'react-test-renderer';
|
|
7
|
+
import App from '../App';
|
|
8
|
+
|
|
9
|
+
// Mock the NavigatorProvider to avoid complex navigation setup
|
|
10
|
+
jest.mock('@idealyst/navigation', () => ({
|
|
11
|
+
NavigatorProvider: ({ children }: { children?: React.ReactNode }) => (
|
|
12
|
+
React.createElement('View', { testID: 'navigator-provider' }, children || 'Navigator Content')
|
|
13
|
+
),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('@idealyst/navigation/examples', () => ({
|
|
17
|
+
ExampleStackRouter: {},
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
describe('App Component', () => {
|
|
21
|
+
it('renders correctly', async () => {
|
|
22
|
+
let component: ReactTestRenderer.ReactTestRenderer;
|
|
23
|
+
|
|
24
|
+
await ReactTestRenderer.act(() => {
|
|
25
|
+
component = ReactTestRenderer.create(<App />);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(component!).toBeDefined();
|
|
29
|
+
expect(component!.toJSON()).toMatchSnapshot();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders without crashing', () => {
|
|
33
|
+
const tree = ReactTestRenderer.create(<App />);
|
|
34
|
+
expect(tree).toBeDefined();
|
|
35
|
+
expect(tree.root).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('contains NavigatorProvider', () => {
|
|
39
|
+
const tree = ReactTestRenderer.create(<App />);
|
|
40
|
+
const navigatorProvider = tree.root.findByProps({ testID: 'navigator-provider' });
|
|
41
|
+
expect(navigatorProvider).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('has proper SafeAreaView structure', () => {
|
|
45
|
+
const tree = ReactTestRenderer.create(<App />);
|
|
46
|
+
const safeAreaView = tree.root.findByType('SafeAreaView');
|
|
47
|
+
expect(safeAreaView).toBeDefined();
|
|
48
|
+
expect(safeAreaView.props.style).toEqual({ flex: 1 });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Sample Component Tests', () => {
|
|
53
|
+
// Example of testing a simple functional component
|
|
54
|
+
const SimpleButton = ({ title, onPress, disabled = false }: {
|
|
55
|
+
title: string;
|
|
56
|
+
onPress: () => void;
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
}) => {
|
|
59
|
+
return React.createElement(
|
|
60
|
+
'TouchableOpacity',
|
|
61
|
+
{
|
|
62
|
+
testID: 'simple-button',
|
|
63
|
+
onPress: disabled ? undefined : onPress,
|
|
64
|
+
style: { opacity: disabled ? 0.5 : 1 }
|
|
65
|
+
},
|
|
66
|
+
React.createElement('Text', null, title)
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
it('renders button with correct title', () => {
|
|
71
|
+
const mockPress = jest.fn();
|
|
72
|
+
const tree = ReactTestRenderer.create(
|
|
73
|
+
<SimpleButton title="Test Button" onPress={mockPress} />
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const textElement = tree.root.findByType('Text');
|
|
77
|
+
expect(textElement.children).toEqual(['Test Button']);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('handles press events', () => {
|
|
81
|
+
const mockPress = jest.fn();
|
|
82
|
+
const tree = ReactTestRenderer.create(
|
|
83
|
+
<SimpleButton title="Test Button" onPress={mockPress} />
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const button = tree.root.findByProps({ testID: 'simple-button' });
|
|
87
|
+
ReactTestRenderer.act(() => {
|
|
88
|
+
button.props.onPress();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(mockPress).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('disables button when disabled prop is true', () => {
|
|
95
|
+
const mockPress = jest.fn();
|
|
96
|
+
const tree = ReactTestRenderer.create(
|
|
97
|
+
<SimpleButton title="Disabled Button" onPress={mockPress} disabled={true} />
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const button = tree.root.findByProps({ testID: 'simple-button' });
|
|
101
|
+
expect(button.props.onPress).toBeUndefined();
|
|
102
|
+
expect(button.props.style.opacity).toBe(0.5);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('handles component state changes', () => {
|
|
106
|
+
const StatefulComponent = () => {
|
|
107
|
+
const [count, setCount] = React.useState(0);
|
|
108
|
+
|
|
109
|
+
return React.createElement(
|
|
110
|
+
'View',
|
|
111
|
+
{ testID: 'stateful-component' },
|
|
112
|
+
React.createElement('Text', { testID: 'count' }, count.toString()),
|
|
113
|
+
React.createElement(
|
|
114
|
+
'TouchableOpacity',
|
|
115
|
+
{
|
|
116
|
+
testID: 'increment-button',
|
|
117
|
+
onPress: () => setCount(c => c + 1)
|
|
118
|
+
},
|
|
119
|
+
React.createElement('Text', null, 'Increment')
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const tree = ReactTestRenderer.create(<StatefulComponent />);
|
|
125
|
+
|
|
126
|
+
// Check initial state
|
|
127
|
+
const countText = tree.root.findByProps({ testID: 'count' });
|
|
128
|
+
expect(countText.children).toEqual(['0']);
|
|
129
|
+
|
|
130
|
+
// Simulate button press
|
|
131
|
+
const incrementButton = tree.root.findByProps({ testID: 'increment-button' });
|
|
132
|
+
ReactTestRenderer.act(() => {
|
|
133
|
+
incrementButton.props.onPress();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Check updated state
|
|
137
|
+
expect(countText.children).toEqual(['1']);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Sample Native Tests', () => {
|
|
142
|
+
it('should pass a basic test', () => {
|
|
143
|
+
expect(1 + 1).toBe(2);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should handle string operations', () => {
|
|
147
|
+
const greeting = 'Hello World';
|
|
148
|
+
expect(greeting).toContain('World');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should work with arrays', () => {
|
|
152
|
+
const items = [1, 2, 3];
|
|
153
|
+
expect(items).toHaveLength(3);
|
|
154
|
+
expect(items).toContain(2);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example component tests for React Native
|
|
3
|
+
* @format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import ReactTestRenderer from 'react-test-renderer';
|
|
8
|
+
|
|
9
|
+
describe('React Native Component Testing Examples', () => {
|
|
10
|
+
// Example 1: Simple Text Component
|
|
11
|
+
const SimpleText = ({ text, color = 'black' }: { text: string; color?: string }) => {
|
|
12
|
+
return React.createElement('Text', { style: { color }, testID: 'simple-text' }, text);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('SimpleText Component', () => {
|
|
16
|
+
it('renders text correctly', () => {
|
|
17
|
+
const tree = ReactTestRenderer.create(<SimpleText text="Hello World" />);
|
|
18
|
+
const textComponent = tree.root.findByProps({ testID: 'simple-text' });
|
|
19
|
+
expect(textComponent.children).toEqual(['Hello World']);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('applies custom color', () => {
|
|
23
|
+
const tree = ReactTestRenderer.create(<SimpleText text="Colored Text" color="red" />);
|
|
24
|
+
const textComponent = tree.root.findByProps({ testID: 'simple-text' });
|
|
25
|
+
expect(textComponent.props.style.color).toBe('red');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('uses default color when not specified', () => {
|
|
29
|
+
const tree = ReactTestRenderer.create(<SimpleText text="Default Color" />);
|
|
30
|
+
const textComponent = tree.root.findByProps({ testID: 'simple-text' });
|
|
31
|
+
expect(textComponent.props.style.color).toBe('black');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Example 2: Button with Press Handler
|
|
36
|
+
const PressableButton = ({
|
|
37
|
+
title,
|
|
38
|
+
onPress,
|
|
39
|
+
disabled = false
|
|
40
|
+
}: {
|
|
41
|
+
title: string;
|
|
42
|
+
onPress: () => void;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
}) => {
|
|
45
|
+
return React.createElement(
|
|
46
|
+
'TouchableOpacity',
|
|
47
|
+
{
|
|
48
|
+
testID: 'pressable-button',
|
|
49
|
+
onPress: disabled ? undefined : onPress,
|
|
50
|
+
style: { opacity: disabled ? 0.5 : 1 }
|
|
51
|
+
},
|
|
52
|
+
React.createElement('Text', { testID: 'button-text' }, title)
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
describe('PressableButton Component', () => {
|
|
57
|
+
it('renders button with title', () => {
|
|
58
|
+
const mockPress = jest.fn();
|
|
59
|
+
const tree = ReactTestRenderer.create(
|
|
60
|
+
<PressableButton title="Press Me" onPress={mockPress} />
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const buttonText = tree.root.findByProps({ testID: 'button-text' });
|
|
64
|
+
expect(buttonText.children).toEqual(['Press Me']);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('calls onPress when pressed', () => {
|
|
68
|
+
const mockPress = jest.fn();
|
|
69
|
+
const tree = ReactTestRenderer.create(
|
|
70
|
+
<PressableButton title="Press Me" onPress={mockPress} />
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const button = tree.root.findByProps({ testID: 'pressable-button' });
|
|
74
|
+
ReactTestRenderer.act(() => {
|
|
75
|
+
button.props.onPress();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(mockPress).toHaveBeenCalledTimes(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('does not call onPress when disabled', () => {
|
|
82
|
+
const mockPress = jest.fn();
|
|
83
|
+
const tree = ReactTestRenderer.create(
|
|
84
|
+
<PressableButton title="Disabled" onPress={mockPress} disabled />
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const button = tree.root.findByProps({ testID: 'pressable-button' });
|
|
88
|
+
expect(button.props.onPress).toBeUndefined();
|
|
89
|
+
expect(button.props.style.opacity).toBe(0.5);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Example 3: List Component
|
|
94
|
+
const ItemList = ({ items }: { items: string[] }) => {
|
|
95
|
+
return React.createElement(
|
|
96
|
+
'FlatList',
|
|
97
|
+
{
|
|
98
|
+
testID: 'item-list',
|
|
99
|
+
data: items,
|
|
100
|
+
renderItem: ({ item, index }: { item: string; index: number }) =>
|
|
101
|
+
React.createElement(
|
|
102
|
+
'View',
|
|
103
|
+
{ key: index, testID: `item-${index}` },
|
|
104
|
+
React.createElement('Text', { testID: `item-text-${index}` }, item)
|
|
105
|
+
),
|
|
106
|
+
keyExtractor: (item: string, index: number) => `${item}-${index}`
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
describe('ItemList Component', () => {
|
|
112
|
+
it('renders list with items', () => {
|
|
113
|
+
const items = ['Item 1', 'Item 2', 'Item 3'];
|
|
114
|
+
const tree = ReactTestRenderer.create(<ItemList items={items} />);
|
|
115
|
+
|
|
116
|
+
const flatList = tree.root.findByProps({ testID: 'item-list' });
|
|
117
|
+
expect(flatList.props.data).toEqual(items);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('generates correct key extractor', () => {
|
|
121
|
+
const items = ['Apple', 'Banana'];
|
|
122
|
+
const tree = ReactTestRenderer.create(<ItemList items={items} />);
|
|
123
|
+
|
|
124
|
+
const flatList = tree.root.findByProps({ testID: 'item-list' });
|
|
125
|
+
const keyExtractor = flatList.props.keyExtractor;
|
|
126
|
+
|
|
127
|
+
expect(keyExtractor('Apple', 0)).toBe('Apple-0');
|
|
128
|
+
expect(keyExtractor('Banana', 1)).toBe('Banana-1');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Example 4: Form Input Component
|
|
133
|
+
const TextInput = ({
|
|
134
|
+
value,
|
|
135
|
+
onChangeText,
|
|
136
|
+
placeholder,
|
|
137
|
+
secureTextEntry = false
|
|
138
|
+
}: {
|
|
139
|
+
value: string;
|
|
140
|
+
onChangeText: (text: string) => void;
|
|
141
|
+
placeholder?: string;
|
|
142
|
+
secureTextEntry?: boolean;
|
|
143
|
+
}) => {
|
|
144
|
+
return React.createElement('TextInput', {
|
|
145
|
+
testID: 'text-input',
|
|
146
|
+
value,
|
|
147
|
+
onChangeText,
|
|
148
|
+
placeholder,
|
|
149
|
+
secureTextEntry,
|
|
150
|
+
style: { borderWidth: 1, padding: 10 }
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
describe('TextInput Component', () => {
|
|
155
|
+
it('renders with initial value', () => {
|
|
156
|
+
const mockChange = jest.fn();
|
|
157
|
+
const tree = ReactTestRenderer.create(
|
|
158
|
+
<TextInput value="Initial Value" onChangeText={mockChange} />
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const input = tree.root.findByProps({ testID: 'text-input' });
|
|
162
|
+
expect(input.props.value).toBe('Initial Value');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('calls onChangeText when text changes', () => {
|
|
166
|
+
const mockChange = jest.fn();
|
|
167
|
+
const tree = ReactTestRenderer.create(
|
|
168
|
+
<TextInput value="" onChangeText={mockChange} />
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const input = tree.root.findByProps({ testID: 'text-input' });
|
|
172
|
+
ReactTestRenderer.act(() => {
|
|
173
|
+
input.props.onChangeText('New Text');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(mockChange).toHaveBeenCalledWith('New Text');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('shows placeholder when provided', () => {
|
|
180
|
+
const mockChange = jest.fn();
|
|
181
|
+
const tree = ReactTestRenderer.create(
|
|
182
|
+
<TextInput
|
|
183
|
+
value=""
|
|
184
|
+
onChangeText={mockChange}
|
|
185
|
+
placeholder="Enter text here"
|
|
186
|
+
/>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const input = tree.root.findByProps({ testID: 'text-input' });
|
|
190
|
+
expect(input.props.placeholder).toBe('Enter text here');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('enables secure text entry when specified', () => {
|
|
194
|
+
const mockChange = jest.fn();
|
|
195
|
+
const tree = ReactTestRenderer.create(
|
|
196
|
+
<TextInput
|
|
197
|
+
value=""
|
|
198
|
+
onChangeText={mockChange}
|
|
199
|
+
secureTextEntry
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const input = tree.root.findByProps({ testID: 'text-input' });
|
|
204
|
+
expect(input.props.secureTextEntry).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Example 5: Component with Hooks
|
|
209
|
+
const Counter = ({ initialValue = 0 }: { initialValue?: number }) => {
|
|
210
|
+
const [count, setCount] = React.useState(initialValue);
|
|
211
|
+
|
|
212
|
+
return React.createElement(
|
|
213
|
+
'View',
|
|
214
|
+
{ testID: 'counter-container' },
|
|
215
|
+
React.createElement('Text', { testID: 'counter-value' }, count.toString()),
|
|
216
|
+
React.createElement(
|
|
217
|
+
'TouchableOpacity',
|
|
218
|
+
{
|
|
219
|
+
testID: 'increment-button',
|
|
220
|
+
onPress: () => setCount(prev => prev + 1)
|
|
221
|
+
},
|
|
222
|
+
React.createElement('Text', null, '+')
|
|
223
|
+
),
|
|
224
|
+
React.createElement(
|
|
225
|
+
'TouchableOpacity',
|
|
226
|
+
{
|
|
227
|
+
testID: 'decrement-button',
|
|
228
|
+
onPress: () => setCount(prev => prev - 1)
|
|
229
|
+
},
|
|
230
|
+
React.createElement('Text', null, '-')
|
|
231
|
+
),
|
|
232
|
+
React.createElement(
|
|
233
|
+
'TouchableOpacity',
|
|
234
|
+
{
|
|
235
|
+
testID: 'reset-button',
|
|
236
|
+
onPress: () => setCount(initialValue)
|
|
237
|
+
},
|
|
238
|
+
React.createElement('Text', null, 'Reset')
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
describe('Counter Component', () => {
|
|
244
|
+
it('renders with initial value', () => {
|
|
245
|
+
const tree = ReactTestRenderer.create(<Counter initialValue={5} />);
|
|
246
|
+
const counterValue = tree.root.findByProps({ testID: 'counter-value' });
|
|
247
|
+
expect(counterValue.children).toEqual(['5']);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('increments counter when increment button is pressed', () => {
|
|
251
|
+
const tree = ReactTestRenderer.create(<Counter />);
|
|
252
|
+
const counterValue = tree.root.findByProps({ testID: 'counter-value' });
|
|
253
|
+
const incrementButton = tree.root.findByProps({ testID: 'increment-button' });
|
|
254
|
+
|
|
255
|
+
expect(counterValue.children).toEqual(['0']);
|
|
256
|
+
|
|
257
|
+
ReactTestRenderer.act(() => {
|
|
258
|
+
incrementButton.props.onPress();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(counterValue.children).toEqual(['1']);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('decrements counter when decrement button is pressed', () => {
|
|
265
|
+
const tree = ReactTestRenderer.create(<Counter initialValue={10} />);
|
|
266
|
+
const counterValue = tree.root.findByProps({ testID: 'counter-value' });
|
|
267
|
+
const decrementButton = tree.root.findByProps({ testID: 'decrement-button' });
|
|
268
|
+
|
|
269
|
+
expect(counterValue.children).toEqual(['10']);
|
|
270
|
+
|
|
271
|
+
ReactTestRenderer.act(() => {
|
|
272
|
+
decrementButton.props.onPress();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(counterValue.children).toEqual(['9']);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('resets counter to initial value when reset is pressed', () => {
|
|
279
|
+
const tree = ReactTestRenderer.create(<Counter initialValue={5} />);
|
|
280
|
+
const counterValue = tree.root.findByProps({ testID: 'counter-value' });
|
|
281
|
+
const incrementButton = tree.root.findByProps({ testID: 'increment-button' });
|
|
282
|
+
const resetButton = tree.root.findByProps({ testID: 'reset-button' });
|
|
283
|
+
|
|
284
|
+
// Increment a few times
|
|
285
|
+
ReactTestRenderer.act(() => {
|
|
286
|
+
incrementButton.props.onPress();
|
|
287
|
+
incrementButton.props.onPress();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(counterValue.children).toEqual(['7']);
|
|
291
|
+
|
|
292
|
+
// Reset
|
|
293
|
+
ReactTestRenderer.act(() => {
|
|
294
|
+
resetButton.props.onPress();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
expect(counterValue.children).toEqual(['5']);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'react-native',
|
|
3
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
4
|
+
testMatch: [
|
|
5
|
+
'**/__tests__/**/*.{ts,tsx,js}',
|
|
6
|
+
'**/*.{test,spec}.{ts,tsx,js}'
|
|
7
|
+
],
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
|
10
|
+
},
|
|
11
|
+
transformIgnorePatterns: [
|
|
12
|
+
'node_modules/(?!(react-native|@react-native|@idealyst)/)',
|
|
13
|
+
],
|
|
14
|
+
collectCoverageFrom: [
|
|
15
|
+
'src/**/*.{ts,tsx}',
|
|
16
|
+
'!src/**/*.d.ts',
|
|
17
|
+
'!src/**/index.ts',
|
|
18
|
+
],
|
|
19
|
+
coverageDirectory: 'coverage',
|
|
20
|
+
testEnvironment: 'jsdom',
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Global test setup for React Native tests
|
|
2
|
+
import 'react-native-gesture-handler/jestSetup';
|
|
3
|
+
|
|
4
|
+
// Mock react-native modules that are not available in test environment
|
|
5
|
+
jest.mock('react-native-reanimated', () => {
|
|
6
|
+
const Reanimated = require('react-native-reanimated/mock');
|
|
7
|
+
Reanimated.default.call = () => {};
|
|
8
|
+
return Reanimated;
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Silence the warning: Animated: `useNativeDriver` is not supported
|
|
12
|
+
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
|
|
@@ -3,6 +3,40 @@
|
|
|
3
3
|
"version": "{{version}}",
|
|
4
4
|
"description": "{{description}}",
|
|
5
5
|
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "react-native start",
|
|
8
|
+
"ios": "react-native run-ios",
|
|
9
|
+
"android": "react-native run-android",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch",
|
|
12
|
+
"test:coverage": "jest --coverage",
|
|
13
|
+
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@idealyst/components": "^1.0.21",
|
|
17
|
+
"@idealyst/navigation": "^1.0.21",
|
|
18
|
+
"@idealyst/theme": "^1.0.21",
|
|
19
|
+
"react": "^18.2.0",
|
|
20
|
+
"react-native": "^0.73.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@babel/core": "^7.20.0",
|
|
24
|
+
"@babel/preset-env": "^7.20.0",
|
|
25
|
+
"@babel/runtime": "^7.20.0",
|
|
26
|
+
"@react-native/babel-preset": "^0.73.0",
|
|
27
|
+
"@react-native/eslint-config": "^0.73.0",
|
|
28
|
+
"@react-native/metro-config": "^0.73.0",
|
|
29
|
+
"@react-native/typescript-config": "^0.73.0",
|
|
30
|
+
"@types/jest": "^29.5.12",
|
|
31
|
+
"@types/react": "^18.2.6",
|
|
32
|
+
"@types/react-test-renderer": "^18.0.7",
|
|
33
|
+
"eslint": "^8.19.0",
|
|
34
|
+
"jest": "^29.6.3",
|
|
35
|
+
"metro-react-native-babel-preset": "^0.76.8",
|
|
36
|
+
"prettier": "^2.4.1",
|
|
37
|
+
"react-test-renderer": "^18.2.0",
|
|
38
|
+
"typescript": "^5.0.4"
|
|
39
|
+
},
|
|
6
40
|
"idealyst": {
|
|
7
41
|
"framework": "react-native",
|
|
8
42
|
"version": "1.0.3"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { add_stuff } from '../src/index';
|
|
2
|
+
|
|
3
|
+
describe('Shared Library', () => {
|
|
4
|
+
it('should export add_stuff', () => {
|
|
5
|
+
expect(add_stuff).toBeDefined();
|
|
6
|
+
expect(typeof add_stuff).toBe('string');
|
|
7
|
+
expect(add_stuff).toBe('here');
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('Sample Shared Tests', () => {
|
|
12
|
+
it('should pass a basic test', () => {
|
|
13
|
+
expect(1 + 1).toBe(2);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should handle string operations', () => {
|
|
17
|
+
const testString = 'Hello World';
|
|
18
|
+
expect(testString).toContain('World');
|
|
19
|
+
expect(testString.length).toBe(11);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should work with objects', () => {
|
|
23
|
+
const testObj = { name: 'test', value: 42 };
|
|
24
|
+
expect(testObj).toHaveProperty('name');
|
|
25
|
+
expect(testObj).toHaveProperty('value', 42);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should handle arrays', () => {
|
|
29
|
+
const testArray = [1, 2, 3, 4, 5];
|
|
30
|
+
expect(testArray).toHaveLength(5);
|
|
31
|
+
expect(testArray).toContain(3);
|
|
32
|
+
expect(testArray.filter(x => x > 3)).toEqual([4, 5]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should work with async operations', async () => {
|
|
36
|
+
const result = await Promise.resolve('async test');
|
|
37
|
+
expect(result).toBe('async test');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
roots: ['<rootDir>/src', '<rootDir>/__tests__'],
|
|
6
|
+
testMatch: [
|
|
7
|
+
'**/__tests__/**/*.{ts,tsx,js}',
|
|
8
|
+
'**/*.{test,spec}.{ts,tsx,js}'
|
|
9
|
+
],
|
|
10
|
+
transform: {
|
|
11
|
+
'^.+\\.tsx?$': 'ts-jest',
|
|
12
|
+
},
|
|
13
|
+
collectCoverageFrom: [
|
|
14
|
+
'src/**/*.{ts,tsx}',
|
|
15
|
+
'!src/**/*.d.ts',
|
|
16
|
+
'!src/**/index.ts',
|
|
17
|
+
],
|
|
18
|
+
coverageDirectory: 'coverage',
|
|
19
|
+
coverageReporters: ['text', 'lcov'],
|
|
20
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
|
21
|
+
testTimeout: 10000,
|
|
22
|
+
};
|