@idealyst/cli 1.0.27 → 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/dist/index.js +43 -13
- package/dist/types/generators/utils.d.ts +6 -1
- 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 +13 -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/native/src/App-with-trpc.tsx +0 -13
- 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/web/src/App-with-trpc.tsx +0 -13
- 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/dist/index.js
CHANGED
|
@@ -52,7 +52,10 @@ async function copyTemplate(templatePath, destPath, data) {
|
|
|
52
52
|
await fs.copy(templatePath, destPath, {
|
|
53
53
|
filter: (src) => {
|
|
54
54
|
const relativePath = path.relative(templatePath, src);
|
|
55
|
-
|
|
55
|
+
// Skip App-with-trpc.tsx as it's only copied when tRPC is enabled
|
|
56
|
+
return !relativePath.includes('node_modules') &&
|
|
57
|
+
!relativePath.includes('.git') &&
|
|
58
|
+
!relativePath.endsWith('App-with-trpc.tsx');
|
|
56
59
|
}
|
|
57
60
|
});
|
|
58
61
|
// Process template files
|
|
@@ -134,10 +137,15 @@ function runCommand(command, args, options) {
|
|
|
134
137
|
});
|
|
135
138
|
});
|
|
136
139
|
}
|
|
137
|
-
function getTemplateData(projectName, description, appName) {
|
|
140
|
+
function getTemplateData(projectName, description, appName, workspaceScope) {
|
|
141
|
+
let packageName = createPackageName(projectName);
|
|
142
|
+
// If we have a workspace scope, prefix the package name with it
|
|
143
|
+
if (workspaceScope) {
|
|
144
|
+
packageName = `@${workspaceScope}/${packageName}`;
|
|
145
|
+
}
|
|
138
146
|
return {
|
|
139
147
|
projectName,
|
|
140
|
-
packageName
|
|
148
|
+
packageName,
|
|
141
149
|
version: '1.0.0',
|
|
142
150
|
description: description || `A new Idealyst project: ${projectName}`,
|
|
143
151
|
appName
|
|
@@ -159,6 +167,22 @@ async function isWorkspaceRoot(directory) {
|
|
|
159
167
|
}
|
|
160
168
|
return false;
|
|
161
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Gets the workspace name from the workspace root's package.json
|
|
172
|
+
*/
|
|
173
|
+
async function getWorkspaceName(directory) {
|
|
174
|
+
const packageJsonPath = path.join(directory, 'package.json');
|
|
175
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
176
|
+
try {
|
|
177
|
+
const packageJson = await fs.readJSON(packageJsonPath);
|
|
178
|
+
return packageJson.name || null;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
162
186
|
/**
|
|
163
187
|
* Resolves the correct project path for individual projects (native, web, shared).
|
|
164
188
|
* Individual projects can ONLY be created within an existing workspace.
|
|
@@ -172,12 +196,15 @@ async function resolveProjectPath(projectName, directory) {
|
|
|
172
196
|
`Please first create a workspace with: idealyst init my-workspace\n` +
|
|
173
197
|
`Then navigate to the workspace directory and create your project.`);
|
|
174
198
|
}
|
|
199
|
+
// Get the workspace name to use as scope
|
|
200
|
+
const workspaceScope = await getWorkspaceName(directory);
|
|
175
201
|
// Create project in workspace's packages/ folder
|
|
176
202
|
const packagesDir = path.join(directory, 'packages');
|
|
177
203
|
await fs.ensureDir(packagesDir);
|
|
178
204
|
return {
|
|
179
205
|
projectPath: path.join(packagesDir, projectName),
|
|
180
|
-
workspacePath: `packages/${projectName}
|
|
206
|
+
workspacePath: `packages/${projectName}`,
|
|
207
|
+
workspaceScope
|
|
181
208
|
};
|
|
182
209
|
}
|
|
183
210
|
async function initializeReactNativeProject(projectName, directory, displayName, skipInstall) {
|
|
@@ -219,9 +246,11 @@ async function overlayIdealystFiles(templatePath, projectPath, data) {
|
|
|
219
246
|
filter: (src) => {
|
|
220
247
|
const relativePath = path.relative(templatePath, src);
|
|
221
248
|
// Skip package.json as we'll merge it separately
|
|
249
|
+
// Skip App-with-trpc.tsx as it's only copied when tRPC is enabled
|
|
222
250
|
return !relativePath.includes('node_modules') &&
|
|
223
251
|
!relativePath.includes('.git') &&
|
|
224
|
-
!relativePath.endsWith('package.json')
|
|
252
|
+
!relativePath.endsWith('package.json') &&
|
|
253
|
+
!relativePath.endsWith('App-with-trpc.tsx');
|
|
225
254
|
}
|
|
226
255
|
});
|
|
227
256
|
// Process template files
|
|
@@ -444,6 +473,7 @@ var utils = /*#__PURE__*/Object.freeze({
|
|
|
444
473
|
copyTrpcFiles: copyTrpcFiles,
|
|
445
474
|
createPackageName: createPackageName,
|
|
446
475
|
getTemplateData: getTemplateData,
|
|
476
|
+
getWorkspaceName: getWorkspaceName,
|
|
447
477
|
initializeReactNativeProject: initializeReactNativeProject,
|
|
448
478
|
installDependencies: installDependencies,
|
|
449
479
|
isWorkspaceRoot: isWorkspaceRoot,
|
|
@@ -472,9 +502,9 @@ async function generateNativeProject(options) {
|
|
|
472
502
|
const displayName = appName || name.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
|
|
473
503
|
console.log(chalk.blue(`📱 Creating React Native project: ${name}`));
|
|
474
504
|
console.log(chalk.gray(` App display name: ${displayName}`));
|
|
475
|
-
const { projectPath, workspacePath } = await resolveProjectPath(name, directory);
|
|
505
|
+
const { projectPath, workspacePath, workspaceScope } = await resolveProjectPath(name, directory);
|
|
476
506
|
const templatePath = path.join(__dirname$4, '..', 'templates', 'native');
|
|
477
|
-
const templateData = getTemplateData(name, `React Native app built with Idealyst Framework`, displayName);
|
|
507
|
+
const templateData = getTemplateData(name, `React Native app built with Idealyst Framework`, displayName, workspaceScope || undefined);
|
|
478
508
|
try {
|
|
479
509
|
// Step 1: Update workspace configuration FIRST (before React Native CLI)
|
|
480
510
|
await updateWorkspacePackageJson(workspacePath, directory);
|
|
@@ -530,9 +560,9 @@ async function generateWebProject(options) {
|
|
|
530
560
|
throw new Error(`Invalid project name: ${name}`);
|
|
531
561
|
}
|
|
532
562
|
console.log(chalk.blue(`🌐 Creating React Web project: ${name}`));
|
|
533
|
-
const { projectPath, workspacePath } = await resolveProjectPath(name, directory);
|
|
563
|
+
const { projectPath, workspacePath, workspaceScope } = await resolveProjectPath(name, directory);
|
|
534
564
|
const templatePath = path.join(__dirname$3, '..', 'templates', 'web');
|
|
535
|
-
const templateData = getTemplateData(name, `React web app built with Idealyst Framework
|
|
565
|
+
const templateData = getTemplateData(name, `React web app built with Idealyst Framework`, undefined, workspaceScope || undefined);
|
|
536
566
|
await copyTemplate(templatePath, projectPath, templateData);
|
|
537
567
|
// Handle tRPC setup
|
|
538
568
|
if (withTrpc) {
|
|
@@ -569,9 +599,9 @@ async function generateSharedLibrary(options) {
|
|
|
569
599
|
throw new Error(`Invalid project name: ${name}`);
|
|
570
600
|
}
|
|
571
601
|
console.log(chalk.blue(`📦 Creating shared library: ${name}`));
|
|
572
|
-
const { projectPath, workspacePath } = await resolveProjectPath(name, directory);
|
|
602
|
+
const { projectPath, workspacePath, workspaceScope } = await resolveProjectPath(name, directory);
|
|
573
603
|
const templatePath = path.join(__dirname$2, '..', 'templates', 'shared');
|
|
574
|
-
const templateData = getTemplateData(name, `Shared library built with Idealyst Framework
|
|
604
|
+
const templateData = getTemplateData(name, `Shared library built with Idealyst Framework`, undefined, workspaceScope || undefined);
|
|
575
605
|
await copyTemplate(templatePath, projectPath, templateData);
|
|
576
606
|
await installDependencies(projectPath, skipInstall);
|
|
577
607
|
await updateWorkspacePackageJson(workspacePath, directory);
|
|
@@ -614,9 +644,9 @@ async function generateApiProject(options) {
|
|
|
614
644
|
throw new Error(`Invalid project name: ${name}`);
|
|
615
645
|
}
|
|
616
646
|
console.log(chalk.blue(`🚀 Creating API project: ${name}`));
|
|
617
|
-
const { projectPath, workspacePath } = await resolveProjectPath(name, directory);
|
|
647
|
+
const { projectPath, workspacePath, workspaceScope } = await resolveProjectPath(name, directory);
|
|
618
648
|
const templatePath = path.join(__dirname, '..', 'templates', 'api');
|
|
619
|
-
const templateData = getTemplateData(name, `Clean API server template with tRPC, Prisma, and Zod
|
|
649
|
+
const templateData = getTemplateData(name, `Clean API server template with tRPC, Prisma, and Zod`, undefined, workspaceScope || undefined);
|
|
620
650
|
await copyTemplate(templatePath, projectPath, templateData);
|
|
621
651
|
await installDependencies(projectPath, skipInstall);
|
|
622
652
|
await updateWorkspacePackageJson(workspacePath, directory);
|
|
@@ -9,11 +9,15 @@ export declare function installDependencies(projectPath: string, skipInstall?: b
|
|
|
9
9
|
export declare function runCommand(command: string, args: string[], options: {
|
|
10
10
|
cwd: string;
|
|
11
11
|
}): Promise<void>;
|
|
12
|
-
export declare function getTemplateData(projectName: string, description?: string, appName?: string): TemplateData;
|
|
12
|
+
export declare function getTemplateData(projectName: string, description?: string, appName?: string, workspaceScope?: string): TemplateData;
|
|
13
13
|
/**
|
|
14
14
|
* Detects if we're in a workspace root directory
|
|
15
15
|
*/
|
|
16
16
|
export declare function isWorkspaceRoot(directory: string): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Gets the workspace name from the workspace root's package.json
|
|
19
|
+
*/
|
|
20
|
+
export declare function getWorkspaceName(directory: string): Promise<string | null>;
|
|
17
21
|
/**
|
|
18
22
|
* Resolves the correct project path for individual projects (native, web, shared).
|
|
19
23
|
* Individual projects can ONLY be created within an existing workspace.
|
|
@@ -22,6 +26,7 @@ export declare function isWorkspaceRoot(directory: string): Promise<boolean>;
|
|
|
22
26
|
export declare function resolveProjectPath(projectName: string, directory: string): Promise<{
|
|
23
27
|
projectPath: string;
|
|
24
28
|
workspacePath: string;
|
|
29
|
+
workspaceScope: string | null;
|
|
25
30
|
}>;
|
|
26
31
|
export declare function initializeReactNativeProject(projectName: string, directory: string, displayName?: string, skipInstall?: boolean): Promise<void>;
|
|
27
32
|
export declare function overlayIdealystFiles(templatePath: string, projectPath: string, data: TemplateData): Promise<void>;
|
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);
|
|
@@ -4,10 +4,20 @@
|
|
|
4
4
|
"description": "{{description}}",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"scripts": {
|
|
8
15
|
"build": "tsc",
|
|
9
16
|
"dev": "tsx watch src/server.ts",
|
|
10
17
|
"start": "node dist/server.js",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch",
|
|
20
|
+
"test:coverage": "jest --coverage",
|
|
11
21
|
"db:generate": "prisma generate",
|
|
12
22
|
"db:push": "prisma db push",
|
|
13
23
|
"db:studio": "prisma studio",
|
|
@@ -28,11 +38,14 @@
|
|
|
28
38
|
"devDependencies": {
|
|
29
39
|
"@types/cors": "^2.8.17",
|
|
30
40
|
"@types/express": "^4.17.21",
|
|
41
|
+
"@types/jest": "^29.5.12",
|
|
31
42
|
"@types/node": "^20.10.4",
|
|
32
43
|
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
|
33
44
|
"@typescript-eslint/parser": "^6.13.1",
|
|
34
45
|
"eslint": "^8.54.0",
|
|
46
|
+
"jest": "^29.7.0",
|
|
35
47
|
"prisma": "^5.7.1",
|
|
48
|
+
"ts-jest": "^29.1.2",
|
|
36
49
|
"tsx": "^4.6.2",
|
|
37
50
|
"typescript": "^5.3.3"
|
|
38
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
|
+
});
|