@idealyst/cli 1.0.28 → 1.0.30

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.
Files changed (41) hide show
  1. package/dist/index.js +96 -35
  2. package/dist/types/generators/utils.d.ts +1 -0
  3. package/package.json +9 -5
  4. package/templates/api/__tests__/api.test.ts +26 -0
  5. package/templates/api/jest.config.js +23 -0
  6. package/templates/api/jest.setup.js +9 -0
  7. package/templates/api/package.json +6 -0
  8. package/templates/native/__tests__/App.test.tsx +156 -0
  9. package/templates/native/__tests__/components.test.tsx +300 -0
  10. package/templates/native/jest.config.js +21 -0
  11. package/templates/native/jest.setup.js +12 -0
  12. package/templates/native/package.json +34 -0
  13. package/templates/shared/__tests__/shared.test.ts +39 -0
  14. package/templates/shared/jest.config.js +22 -0
  15. package/templates/shared/package.json +10 -0
  16. package/templates/shared/rollup.config.js +43 -0
  17. package/templates/web/__tests__/App.test.tsx +342 -0
  18. package/templates/web/__tests__/components.test.tsx +564 -0
  19. package/templates/web/jest.config.js +27 -0
  20. package/templates/web/jest.setup.js +24 -0
  21. package/templates/web/package.json +11 -1
  22. package/templates/workspace/.devcontainer/devcontainer.json +150 -0
  23. package/templates/workspace/.devcontainer/post-create.sh +129 -0
  24. package/templates/workspace/.dockerignore +151 -0
  25. package/templates/workspace/.env.example +36 -0
  26. package/templates/workspace/.env.production +56 -0
  27. package/templates/workspace/DOCKER.md +385 -0
  28. package/templates/workspace/Dockerfile +100 -0
  29. package/templates/workspace/README.md +93 -0
  30. package/templates/workspace/docker/nginx/prod.conf +238 -0
  31. package/templates/workspace/docker/nginx.conf +131 -0
  32. package/templates/workspace/docker/postgres/init.sql +41 -0
  33. package/templates/workspace/docker/prometheus/prometheus.yml +52 -0
  34. package/templates/workspace/docker-compose.prod.yml +146 -0
  35. package/templates/workspace/docker-compose.yml +144 -0
  36. package/templates/workspace/jest.config.js +20 -0
  37. package/templates/workspace/package.json +11 -1
  38. package/templates/workspace/scripts/docker/db-backup.sh +230 -0
  39. package/templates/workspace/scripts/docker/deploy.sh +212 -0
  40. package/templates/workspace/scripts/test-runner.js +120 -0
  41. package/templates/workspace/setup.sh +205 -0
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import path from 'path';
5
- import { fileURLToPath } from 'url';
6
5
  import fs from 'fs-extra';
6
+ import { fileURLToPath } from 'url';
7
7
  import { spawn } from 'child_process';
8
8
  import ora from 'ora';
9
9
  import validatePackageName from 'validate-npm-package-name';
@@ -119,12 +119,23 @@ async function installDependencies(projectPath, skipInstall = false) {
119
119
  }
120
120
  function runCommand(command, args, options) {
121
121
  return new Promise((resolve, reject) => {
122
+ const timeoutMs = options.timeout || 300000; // 5 minutes default timeout
122
123
  const process = spawn(command, args, {
123
124
  cwd: options.cwd,
124
- stdio: 'inherit',
125
+ stdio: ['pipe', 'inherit', 'inherit'], // Pipe stdin to prevent hanging on prompts
125
126
  shell: true
126
127
  });
128
+ // Set up timeout
129
+ const timeoutId = setTimeout(() => {
130
+ process.kill('SIGTERM');
131
+ reject(new Error(`Command timed out after ${timeoutMs / 1000} seconds`));
132
+ }, timeoutMs);
133
+ // Close stdin immediately to prevent hanging on interactive prompts
134
+ if (process.stdin) {
135
+ process.stdin.end();
136
+ }
127
137
  process.on('close', (code) => {
138
+ clearTimeout(timeoutId);
128
139
  if (code === 0) {
129
140
  resolve();
130
141
  }
@@ -133,6 +144,7 @@ function runCommand(command, args, options) {
133
144
  }
134
145
  });
135
146
  process.on('error', (error) => {
147
+ clearTimeout(timeoutId);
136
148
  reject(error);
137
149
  });
138
150
  });
@@ -210,9 +222,15 @@ async function resolveProjectPath(projectName, directory) {
210
222
  async function initializeReactNativeProject(projectName, directory, displayName, skipInstall) {
211
223
  const spinner = ora('Initializing React Native project...').start();
212
224
  try {
213
- // Use the correct React Native CLI command format with specific version and yarn
225
+ // Use create-react-native-app for a more reliable setup
214
226
  const cliCommand = 'npx';
215
- const args = ['@react-native-community/cli@latest', 'init', projectName, '--version', '0.80.1', '--pm', 'yarn', '--skip-git-init'];
227
+ const args = [
228
+ 'react-native@latest',
229
+ 'init',
230
+ projectName,
231
+ '--pm', 'yarn',
232
+ '--skip-git-init'
233
+ ];
216
234
  // Add title if displayName is provided
217
235
  if (displayName) {
218
236
  args.push('--title', displayName);
@@ -221,19 +239,31 @@ async function initializeReactNativeProject(projectName, directory, displayName,
221
239
  if (skipInstall) {
222
240
  args.push('--skip-install');
223
241
  }
224
- // Run React Native initialization in the target directory
225
- await runCommand(cliCommand, args, { cwd: directory });
242
+ spinner.text = 'Initializing React Native project (this may take a few minutes)...';
243
+ // Run React Native initialization with timeout
244
+ await runCommand(cliCommand, args, {
245
+ cwd: directory,
246
+ timeout: 600000 // 10 minutes timeout for React Native init
247
+ });
226
248
  spinner.succeed('React Native project initialized successfully');
227
249
  }
228
250
  catch (error) {
229
251
  spinner.fail('Failed to initialize React Native project');
230
- console.log(chalk.yellow('Make sure you have the React Native CLI and yarn available:'));
231
- console.log(chalk.white(' npx @react-native-community/cli@latest init ProjectName --version 0.80.1 --pm yarn --skip-git-init'));
232
- console.log(chalk.yellow('If you encounter issues, try:'));
233
- console.log(chalk.white(' npm install -g @react-native-community/cli'));
234
- console.log(chalk.white(' npm install -g yarn'));
235
- console.log(chalk.white(' # or'));
236
- console.log(chalk.white(' yarn global add @react-native-community/cli'));
252
+ if (error instanceof Error && error.message.includes('timed out')) {
253
+ console.log(chalk.red(' React Native initialization timed out'));
254
+ console.log(chalk.yellow('This can happen due to:'));
255
+ console.log(chalk.white(' Slow internet connection'));
256
+ console.log(chalk.white(' Network issues downloading dependencies'));
257
+ console.log(chalk.white(' React Native CLI hanging on prompts'));
258
+ }
259
+ console.log(chalk.yellow('\n💡 Alternative approaches:'));
260
+ console.log(chalk.white('1. Try manually creating the project:'));
261
+ console.log(chalk.white(` npx react-native@latest init ${projectName} --pm yarn --skip-git-init`));
262
+ console.log(chalk.white('\n2. Use Expo (faster alternative):'));
263
+ console.log(chalk.white(` npx create-expo-app@latest ${projectName} --template blank-typescript`));
264
+ console.log(chalk.white('\n3. Ensure prerequisites:'));
265
+ console.log(chalk.white(' npm install -g react-native-cli'));
266
+ console.log(chalk.white(' npm install -g @react-native-community/cli'));
237
267
  throw error;
238
268
  }
239
269
  }
@@ -506,43 +536,70 @@ async function generateNativeProject(options) {
506
536
  const templatePath = path.join(__dirname$4, '..', 'templates', 'native');
507
537
  const templateData = getTemplateData(name, `React Native app built with Idealyst Framework`, displayName, workspaceScope || undefined);
508
538
  try {
509
- // Step 1: Update workspace configuration FIRST (before React Native CLI)
539
+ // Step 1: Update workspace configuration FIRST
510
540
  await updateWorkspacePackageJson(workspacePath, directory);
511
- // Step 2: Initialize React Native project using CLI with --skip-install
512
- // Note: For React Native CLI, we need to run it in the parent directory and specify the project name
513
- const projectDir = path.dirname(projectPath);
514
- const projectName = path.basename(projectPath);
515
- await initializeReactNativeProject(projectName, projectDir, displayName, true);
516
- // Step 3: Overlay Idealyst-specific files
517
- await overlayIdealystFiles(templatePath, projectPath, templateData);
541
+ // Step 2: Try React Native CLI initialization, with fallback to template-only
542
+ const useRnCli = process.env.IDEALYST_USE_RN_CLI !== 'false';
543
+ if (useRnCli) {
544
+ try {
545
+ console.log(chalk.blue('🚀 Attempting React Native CLI initialization...'));
546
+ console.log(chalk.gray(' (This creates proper Android/iOS native directories)'));
547
+ // Initialize React Native project using CLI with --skip-install
548
+ const projectDir = path.dirname(projectPath);
549
+ const projectName = path.basename(projectPath);
550
+ await initializeReactNativeProject(projectName, projectDir, displayName, true);
551
+ // Step 3: Overlay Idealyst-specific files
552
+ await overlayIdealystFiles(templatePath, projectPath, templateData);
553
+ console.log(chalk.green('✅ React Native project created with native platform support'));
554
+ }
555
+ catch (rnError) {
556
+ console.log(chalk.yellow('⚠️ React Native CLI failed, falling back to template-only approach...'));
557
+ await createNativeProjectFromTemplate(templatePath, projectPath, templateData);
558
+ console.log(chalk.yellow('📝 Template-only project created. You may need to run "npx react-native init" later for native platforms.'));
559
+ }
560
+ }
561
+ else {
562
+ console.log(chalk.blue('��️ Creating project from template only (IDEALYST_USE_RN_CLI=false)'));
563
+ await createNativeProjectFromTemplate(templatePath, projectPath, templateData);
564
+ }
518
565
  // Step 4: Handle tRPC setup
519
566
  if (withTrpc) {
520
567
  await copyTrpcFiles(templatePath, projectPath, templateData);
521
568
  await copyTrpcAppComponent(templatePath, projectPath, templateData);
522
569
  }
523
- // Step 5: Configure Android vector icons
524
- await configureAndroidVectorIcons(projectPath);
525
- // Step 6: Remove tRPC dependencies if not requested (after merge but before install)
570
+ // Step 5: Configure Android vector icons (only if we have Android directory)
571
+ const hasAndroid = await fs.pathExists(path.join(projectPath, 'android'));
572
+ if (hasAndroid) {
573
+ await configureAndroidVectorIcons(projectPath);
574
+ }
575
+ // Step 6: Remove tRPC dependencies if not requested
526
576
  if (!withTrpc) {
527
577
  await removeTrpcDependencies(projectPath);
528
578
  }
529
- // Step 7: Install dependencies (including Idealyst packages) after workspace config is updated
579
+ // Step 7: Install dependencies
530
580
  await installDependencies(projectPath, skipInstall);
531
581
  console.log(chalk.green('✅ React Native project created successfully!'));
532
582
  console.log(chalk.blue('📋 Project includes:'));
533
- console.log(chalk.white(' • React Native with proper Android/iOS setup'));
534
- console.log(chalk.white(' • Idealyst Components'));
535
- console.log(chalk.white(' • Idealyst Navigation'));
536
- console.log(chalk.white(' • Idealyst Theme'));
537
- console.log(chalk.white(' • React Native Vector Icons (configured)'));
538
- console.log(chalk.white(' • TypeScript configuration'));
539
- console.log(chalk.white(' • Metro configuration'));
540
- console.log(chalk.white(' • Babel configuration'));
541
- console.log(chalk.white(' • Native platform directories (android/, ios/)'));
583
+ console.log(chalk.white(' • React Native with TypeScript'));
584
+ console.log(chalk.white(' • Idealyst Components & Navigation'));
585
+ console.log(chalk.white(' • Idealyst Theme & Styling'));
586
+ console.log(chalk.white(' • Jest testing configuration'));
587
+ console.log(chalk.white(' • Metro & Babel configuration'));
588
+ if (hasAndroid) {
589
+ console.log(chalk.white(' • Android native platform setup'));
590
+ console.log(chalk.white(' • React Native Vector Icons (configured)'));
591
+ }
592
+ const hasIos = await fs.pathExists(path.join(projectPath, 'ios'));
593
+ if (hasIos) {
594
+ console.log(chalk.white(' • iOS native platform setup'));
595
+ }
596
+ if (!hasAndroid && !hasIos) {
597
+ console.log(chalk.yellow(' ⚠️ No native platforms detected'));
598
+ console.log(chalk.yellow(' Run "npx react-native init" in project directory for native support'));
599
+ }
542
600
  if (withTrpc) {
543
601
  console.log(chalk.white(' • tRPC client setup and utilities'));
544
602
  console.log(chalk.white(' • React Query integration'));
545
- console.log(chalk.white(' • Pre-configured tRPC provider'));
546
603
  }
547
604
  }
548
605
  catch (error) {
@@ -551,6 +608,10 @@ async function generateNativeProject(options) {
551
608
  throw error;
552
609
  }
553
610
  }
611
+ // Helper function to create project from template only (fallback when RN CLI fails)
612
+ async function createNativeProjectFromTemplate(templatePath, projectPath, templateData) {
613
+ await copyTemplate(templatePath, projectPath, templateData);
614
+ }
554
615
 
555
616
  const __filename$3 = fileURLToPath(import.meta.url);
556
617
  const __dirname$3 = path.dirname(__filename$3);
@@ -8,6 +8,7 @@ export declare function processTemplateFile(filePath: string, data: TemplateData
8
8
  export declare function installDependencies(projectPath: string, skipInstall?: boolean): Promise<void>;
9
9
  export declare function runCommand(command: string, args: string[], options: {
10
10
  cwd: string;
11
+ timeout?: number;
11
12
  }): Promise<void>;
12
13
  export declare function getTemplateData(projectName: string, description?: string, appName?: string, workspaceScope?: string): TemplateData;
13
14
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/cli",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "CLI tool for generating Idealyst Framework projects",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,10 +19,11 @@
19
19
  "access": "public"
20
20
  },
21
21
  "scripts": {
22
- "build": "rollup -c",
23
- "dev": "rollup -c -w",
24
- "prepublishOnly": "yarn build",
25
- "publish:npm": "npm publish"
22
+ "build": "tsc",
23
+ "test": "jest",
24
+ "test:integration": "jest __tests__/integration.test.ts",
25
+ "dev": "tsc --watch",
26
+ "clean": "rm -rf dist"
26
27
  },
27
28
  "dependencies": {
28
29
  "chalk": "^5.0.0",
@@ -35,13 +36,16 @@
35
36
  "devDependencies": {
36
37
  "@types/fs-extra": "^11.0.0",
37
38
  "@types/inquirer": "^9.0.0",
39
+ "@types/jest": "^29.5.0",
38
40
  "@types/node": "^20.0.0",
39
41
  "@types/validate-npm-package-name": "^4.0.0",
42
+ "jest": "^29.7.0",
40
43
  "rollup": "^3.20.0",
41
44
  "rollup-plugin-commonjs": "^10.1.0",
42
45
  "rollup-plugin-json": "^4.0.0",
43
46
  "rollup-plugin-node-resolve": "^5.2.0",
44
47
  "rollup-plugin-typescript2": "^0.34.0",
48
+ "ts-jest": "^29.1.0",
45
49
  "typescript": "^5.0.0"
46
50
  },
47
51
  "files": [
@@ -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
+ });