Skip to content

Commit 5a682e8

Browse files
committed
feat: implement CI/CD pipeline with GitHub Actions
1 parent 829f395 commit 5a682e8

11 files changed

+296
-9
lines changed

.github/workflows/ci.yml

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: PowerPulse CI/CD
2+
3+
on:
4+
push:
5+
branches: [ main, develop, 'release/**' ]
6+
pull_request:
7+
branches: [ main, develop ]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
- name: Set up Node.js
15+
uses: actions/setup-node@v3
16+
with:
17+
node-version: '18'
18+
cache: 'npm'
19+
- name: Install dependencies
20+
run: npm run install-all
21+
- name: Lint client code
22+
run: cd client && npm run lint
23+
- name: Lint server code
24+
run: cd server && npm run lint
25+
26+
test:
27+
runs-on: ubuntu-latest
28+
needs: lint
29+
steps:
30+
- uses: actions/checkout@v3
31+
- name: Set up Node.js
32+
uses: actions/setup-node@v3
33+
with:
34+
node-version: '18'
35+
cache: 'npm'
36+
- name: Install dependencies
37+
run: npm run install-all
38+
- name: Run client tests
39+
run: cd client && npm test
40+
- name: Run server tests
41+
run: cd server && npm test
42+
43+
build:
44+
runs-on: ubuntu-latest
45+
needs: test
46+
steps:
47+
- uses: actions/checkout@v3
48+
- name: Set up Node.js
49+
uses: actions/setup-node@v3
50+
with:
51+
node-version: '18'
52+
cache: 'npm'
53+
- name: Install dependencies
54+
run: npm run install-all
55+
- name: Build client
56+
run: cd client && npm run build
57+
- name: Upload build artifacts
58+
uses: actions/upload-artifact@v3
59+
with:
60+
name: build-files
61+
path: client/dist
62+
63+
# Optional: Add deployment job for production releases
64+
deploy:
65+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
66+
runs-on: ubuntu-latest
67+
needs: build
68+
steps:
69+
- name: Download build artifacts
70+
uses: actions/download-artifact@v3
71+
with:
72+
name: build-files
73+
path: client/dist
74+
# Add deployment steps here (e.g., to GitHub Pages, Docker Hub, or your hosting provider)

CONTRIBUTING.md

+36-5
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,44 @@ Common types are:
105105

106106
## Testing Your Stuff
107107

108-
- Try to write tests for new features
109-
- Run the existing tests before submitting:
110-
```
111-
npm test
112-
```
108+
We have a comprehensive testing setup with Jest for the server and Vitest for the client:
109+
110+
### Client Testing
111+
- Run client tests: `cd client && npm test`
112+
- Watch mode: `cd client && npm run test:watch`
113+
- Coverage report: `cd client && npm run test:coverage`
114+
115+
### Server Testing
116+
- Run server tests: `cd server && npm test`
117+
- Watch mode: `cd server && npm run test:watch`
118+
- Coverage report: `cd server && npm run test:coverage`
119+
120+
### Writing Tests
121+
- Client tests go in `client/src/test` or alongside components with `.test.jsx` extension
122+
- Server tests go in `server/tests` with `.test.js` extension
123+
- Focus on testing functionality, not implementation details
113124
- Don't worry about 100% coverage - just test the important parts
114125

126+
## Linting
127+
128+
We use ESLint to maintain code quality:
129+
130+
- Lint client code: `cd client && npm run lint`
131+
- Fix client code: `cd client && npm run lint:fix`
132+
- Lint server code: `cd server && npm run lint`
133+
- Fix server code: `cd server && npm run lint:fix`
134+
135+
## CI/CD Pipeline
136+
137+
We use GitHub Actions for our CI/CD pipeline:
138+
139+
- Every PR and push to main/develop branches triggers the pipeline
140+
- The pipeline runs linting, tests, and builds the application
141+
- All checks must pass before a PR can be merged
142+
- Tagged releases are automatically built and prepared for deployment
143+
144+
When you submit a PR, check the GitHub Actions tab to see if your changes pass all checks.
145+
115146
## Updating Docs
116147

117148
If you're changing how something works:

client/.eslintrc.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module.exports = {
2+
env: {
3+
browser: true,
4+
es2021: true,
5+
node: true,
6+
},
7+
extends: [
8+
'eslint:recommended',
9+
'plugin:react/recommended',
10+
'plugin:react-hooks/recommended',
11+
],
12+
parserOptions: {
13+
ecmaFeatures: {
14+
jsx: true,
15+
},
16+
ecmaVersion: 'latest',
17+
sourceType: 'module',
18+
},
19+
plugins: ['react', 'react-hooks'],
20+
settings: {
21+
react: {
22+
version: 'detect',
23+
},
24+
},
25+
rules: {
26+
'react/react-in-jsx-scope': 'off', // Not needed in React 17+
27+
'react/prop-types': 'warn', // Warn about missing prop types
28+
'no-unused-vars': 'warn', // Warn about unused variables
29+
'react-hooks/rules-of-hooks': 'error', // Enforce Rules of Hooks
30+
'react-hooks/exhaustive-deps': 'warn', // Warn about missing dependencies
31+
'no-console': ['warn', { allow: ['warn', 'error'] }], // Warn about console.log, but allow console.warn and console.error
32+
},
33+
};

client/package.json

+14-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
"scripts": {
1212
"dev": "vite",
1313
"build": "vite build",
14-
"preview": "vite preview"
14+
"preview": "vite preview",
15+
"lint": "eslint src --ext .js,.jsx",
16+
"lint:fix": "eslint src --ext .js,.jsx --fix",
17+
"test": "vitest run",
18+
"test:watch": "vitest",
19+
"test:coverage": "vitest run --coverage"
1520
},
1621
"dependencies": {
1722
"axios": "^1.7.9",
@@ -24,12 +29,19 @@
2429
"react-router-dom": "^6.10.0"
2530
},
2631
"devDependencies": {
32+
"@testing-library/jest-dom": "^6.1.4",
33+
"@testing-library/react": "^14.0.0",
2734
"@types/react": "^18.0.28",
2835
"@types/react-dom": "^18.0.11",
2936
"@vitejs/plugin-react": "^3.1.0",
3037
"autoprefixer": "^10.4.14",
38+
"eslint": "^8.38.0",
39+
"eslint-plugin-react": "^7.32.2",
40+
"eslint-plugin-react-hooks": "^4.6.0",
41+
"jsdom": "^22.1.0",
3142
"postcss": "^8.4.21",
3243
"tailwindcss": "^3.3.1",
33-
"vite": "^4.2.0"
44+
"vite": "^4.2.0",
45+
"vitest": "^0.34.6"
3446
}
3547
}

client/src/test/example.test.jsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { render, screen } from '@testing-library/react';
3+
import appConfig from '../config/appConfig';
4+
5+
// This is a simple example test to demonstrate testing setup
6+
describe('Application Configuration', () => {
7+
it('has the correct version number', () => {
8+
expect(appConfig.version).toBe('1.8.2');
9+
});
10+
11+
it('has the correct app name', () => {
12+
expect(appConfig.appName).toBe('PowerPulse');
13+
});
14+
15+
it('has a valid GitHub URL', () => {
16+
expect(appConfig.githubUrl).toMatch(/^https:\/\/github\.com\//);
17+
});
18+
});
19+
20+
// Example of how to test a component (you would need to create this component)
21+
/*
22+
import ExampleButton from '../components/ExampleButton';
23+
24+
describe('ExampleButton Component', () => {
25+
it('renders correctly', () => {
26+
render(<ExampleButton label="Click me" />);
27+
expect(screen.getByText('Click me')).toBeInTheDocument();
28+
});
29+
30+
it('has the correct CSS class', () => {
31+
render(<ExampleButton label="Click me" />);
32+
const button = screen.getByText('Click me');
33+
expect(button).toHaveClass('btn');
34+
});
35+
});
36+
*/

client/src/test/setup.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { expect, afterEach } from 'vitest';
2+
import { cleanup } from '@testing-library/react';
3+
import matchers from '@testing-library/jest-dom/matchers';
4+
5+
// Extend Vitest's expect method with methods from react-testing-library
6+
expect.extend(matchers);
7+
8+
// Runs a cleanup after each test case (e.g. clearing jsdom)
9+
afterEach(() => {
10+
cleanup();
11+
});

client/vitest.config.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineConfig } from 'vitest/config';
2+
import react from '@vitejs/plugin-react';
3+
4+
export default defineConfig({
5+
plugins: [react()],
6+
test: {
7+
globals: true,
8+
environment: 'jsdom',
9+
setupFiles: ['./src/test/setup.js'],
10+
coverage: {
11+
reporter: ['text', 'json', 'html'],
12+
exclude: ['node_modules/', 'src/test/'],
13+
},
14+
},
15+
});

server/.eslintrc.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = {
2+
env: {
3+
node: true,
4+
es2021: true,
5+
jest: true,
6+
},
7+
extends: ['eslint:recommended'],
8+
parserOptions: {
9+
ecmaVersion: 'latest',
10+
sourceType: 'module',
11+
},
12+
rules: {
13+
'no-unused-vars': 'warn', // Warn about unused variables
14+
'no-console': 'off', // Allow console in server code
15+
'no-process-exit': 'warn', // Warn about process.exit()
16+
'no-useless-escape': 'warn', // Warn about unnecessary escape characters
17+
'no-empty': 'warn', // Warn about empty blocks
18+
'prefer-const': 'warn', // Prefer const over let when possible
19+
'no-var': 'warn', // Prefer let/const over var
20+
},
21+
};

server/jest.config.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
verbose: true,
4+
collectCoverage: false,
5+
coverageDirectory: 'coverage',
6+
coveragePathIgnorePatterns: ['/node_modules/', '/tests/'],
7+
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
8+
testPathIgnorePatterns: ['/node_modules/'],
9+
transform: {},
10+
// Since we're using CommonJS in the server, we don't need to transform
11+
// If you switch to ES modules, you might need to add transformers
12+
};

server/package.json

+9-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
"scripts": {
1212
"start": "node server.js",
1313
"dev": "nodemon server.js",
14-
"test": "echo \"Error: no test specified\" && exit 1"
14+
"test": "jest",
15+
"test:watch": "jest --watch",
16+
"test:coverage": "jest --coverage",
17+
"lint": "eslint . --ext .js",
18+
"lint:fix": "eslint . --ext .js --fix"
1519
},
1620
"keywords": [
1721
"ups",
@@ -32,6 +36,9 @@
3236
"sqlite3": "^5.1.6"
3337
},
3438
"devDependencies": {
35-
"nodemon": "^2.0.22"
39+
"eslint": "^8.38.0",
40+
"jest": "^29.5.0",
41+
"nodemon": "^2.0.22",
42+
"supertest": "^6.3.3"
3643
}
3744
}

server/tests/example.test.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const { describe, it, expect } = require('@jest/globals');
2+
3+
// This is a simple example test to demonstrate testing setup
4+
describe('Server Environment', () => {
5+
it('has the correct NODE_ENV', () => {
6+
// During tests, NODE_ENV should be 'test'
7+
expect(process.env.NODE_ENV).not.toBe('production');
8+
});
9+
10+
it('can perform basic assertions', () => {
11+
expect(1 + 1).toBe(2);
12+
expect('PowerPulse').toContain('Power');
13+
expect({ name: 'PowerPulse' }).toHaveProperty('name');
14+
});
15+
});
16+
17+
// Example of how to test an API endpoint (you would implement this)
18+
/*
19+
const request = require('supertest');
20+
const app = require('../app'); // Import your Express app
21+
22+
describe('API Endpoints', () => {
23+
it('GET /api/status returns 200', async () => {
24+
const response = await request(app).get('/api/status');
25+
expect(response.statusCode).toBe(200);
26+
expect(response.body).toHaveProperty('status', 'ok');
27+
});
28+
29+
it('GET /api/version returns correct version', async () => {
30+
const response = await request(app).get('/api/version');
31+
expect(response.statusCode).toBe(200);
32+
expect(response.body).toHaveProperty('version', '1.8.2');
33+
});
34+
});
35+
*/

0 commit comments

Comments
 (0)