feat: Add testing infrastructure and documentation
- Set up Vitest for unit testing with jsdom - Add test setup with Web Audio API and requestAnimationFrame mocks - Create initial test suites for DOM and animations modules - Add test scripts to package.json (test, test:ui, test:run, coverage) - Update CI workflow to include test execution - Create CONTRIBUTING.md with conventional commits guidelines - Create SECURITY.md with security policy - Update ESLint config to support test files - All tests passing (8/8) Co-authored-by: ZaneThePython <102631678+ZaneThePython@users.noreply.github.com>
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-and-build:
|
lint-test-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@@ -33,6 +33,9 @@ jobs:
|
|||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: npm run format:check
|
run: npm run format:check
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test:run
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
|
|||||||
179
CONTRIBUTING.md
Normal file
179
CONTRIBUTING.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Contributing to ZanePersonal
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to this project! While this is primarily a personal website, contributions are welcome.
|
||||||
|
|
||||||
|
## Commit Message Format
|
||||||
|
|
||||||
|
This project follows the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages.
|
||||||
|
|
||||||
|
### Format
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
- **feat**: A new feature
|
||||||
|
- **fix**: A bug fix
|
||||||
|
- **docs**: Documentation changes
|
||||||
|
- **style**: Code style changes (formatting, missing semi-colons, etc.)
|
||||||
|
- **refactor**: Code refactoring without changing functionality
|
||||||
|
- **perf**: Performance improvements
|
||||||
|
- **test**: Adding or updating tests
|
||||||
|
- **build**: Changes to build system or dependencies
|
||||||
|
- **ci**: Changes to CI configuration
|
||||||
|
- **chore**: Other changes that don't modify src or test files
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(effects): add new particle collision detection
|
||||||
|
|
||||||
|
fix(animations): resolve timing issue with typewriter effect
|
||||||
|
|
||||||
|
docs(readme): update installation instructions
|
||||||
|
|
||||||
|
style(css): apply consistent naming convention
|
||||||
|
|
||||||
|
refactor(modules): extract sound utilities to separate module
|
||||||
|
|
||||||
|
perf(effects): optimize particle rendering loop
|
||||||
|
|
||||||
|
test(interactions): add unit tests for ripple effect
|
||||||
|
|
||||||
|
build(vite): update build configuration for better tree-shaking
|
||||||
|
|
||||||
|
ci(github-actions): add accessibility testing workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. **Fork the repository** (if you're an external contributor)
|
||||||
|
|
||||||
|
2. **Clone your fork**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/ZanePersonal.git
|
||||||
|
cd ZanePersonal
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Install dependencies**
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Create a feature branch**
|
||||||
|
```bash
|
||||||
|
git checkout -b feat/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Make your changes**
|
||||||
|
- Write clean, readable code
|
||||||
|
- Follow existing code style
|
||||||
|
- Add comments where necessary
|
||||||
|
- Update documentation if needed
|
||||||
|
|
||||||
|
6. **Run quality checks**
|
||||||
|
```bash
|
||||||
|
npm run lint # Check for linting errors
|
||||||
|
npm run format # Format code with Prettier
|
||||||
|
npm run build # Ensure project builds successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Commit your changes**
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "feat(scope): your descriptive message"
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **Push to your fork**
|
||||||
|
```bash
|
||||||
|
git push origin feat/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **Create a Pull Request**
|
||||||
|
- Provide a clear description of changes
|
||||||
|
- Reference any related issues
|
||||||
|
- Ensure CI checks pass
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
|
||||||
|
- Use ES6+ features
|
||||||
|
- Prefer `const` over `let`, avoid `var`
|
||||||
|
- Use arrow functions for callbacks
|
||||||
|
- Keep functions small and focused
|
||||||
|
- Add JSDoc comments for complex functions
|
||||||
|
- Follow the existing modular structure
|
||||||
|
|
||||||
|
### CSS
|
||||||
|
|
||||||
|
- Use CSS custom properties (variables) defined in `:root`
|
||||||
|
- Follow BEM-like naming conventions where appropriate
|
||||||
|
- Group related properties together
|
||||||
|
- Use meaningful class names
|
||||||
|
- Avoid overly specific selectors
|
||||||
|
|
||||||
|
### HTML
|
||||||
|
|
||||||
|
- Use semantic HTML5 elements
|
||||||
|
- Include proper ARIA labels for accessibility
|
||||||
|
- Ensure all images have alt text
|
||||||
|
- Maintain proper heading hierarchy
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
While this project doesn't currently have automated tests, please ensure:
|
||||||
|
- All features work in modern browsers (Chrome, Firefox, Safari, Edge)
|
||||||
|
- Responsive design works on mobile, tablet, and desktop
|
||||||
|
- No console errors or warnings
|
||||||
|
- Accessibility features work with keyboard navigation
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- Keep total bundle size under 150KB (compressed)
|
||||||
|
- Optimize images before adding them
|
||||||
|
- Minimize use of heavy libraries
|
||||||
|
- Test animations on lower-end devices
|
||||||
|
- Use lazy loading where appropriate
|
||||||
|
|
||||||
|
## Accessibility Standards
|
||||||
|
|
||||||
|
- Maintain WCAG 2.1 Level AA compliance
|
||||||
|
- Test with screen readers
|
||||||
|
- Ensure keyboard navigation works
|
||||||
|
- Provide sufficient color contrast
|
||||||
|
- Include focus indicators on interactive elements
|
||||||
|
|
||||||
|
## Pull Request Checklist
|
||||||
|
|
||||||
|
Before submitting a PR, ensure:
|
||||||
|
|
||||||
|
- [ ] Code follows the project's style guidelines
|
||||||
|
- [ ] All linting checks pass (`npm run lint`)
|
||||||
|
- [ ] Code is properly formatted (`npm run format`)
|
||||||
|
- [ ] Project builds successfully (`npm run build`)
|
||||||
|
- [ ] Changes are tested in multiple browsers
|
||||||
|
- [ ] Documentation is updated if needed
|
||||||
|
- [ ] Commit messages follow conventional commits format
|
||||||
|
- [ ] PR description clearly explains the changes
|
||||||
|
|
||||||
|
## Questions or Issues?
|
||||||
|
|
||||||
|
Feel free to:
|
||||||
|
- Open an issue for bugs or feature requests
|
||||||
|
- Start a discussion for questions
|
||||||
|
- Email [contact@zane.org](mailto:contact@zane.org)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing, you agree that your contributions will be licensed under the MIT License.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Thank you for contributing! 🎉
|
||||||
71
SECURITY.md
Normal file
71
SECURITY.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
This is a personal website project. The latest version on the `main` branch is always supported.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| Latest | :white_check_mark: |
|
||||||
|
| Older | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you discover a security vulnerability in this project, please report it responsibly:
|
||||||
|
|
||||||
|
### Email
|
||||||
|
Send details to [contact@zane.org](mailto:contact@zane.org) with:
|
||||||
|
- Description of the vulnerability
|
||||||
|
- Steps to reproduce
|
||||||
|
- Potential impact
|
||||||
|
- Suggested fix (if any)
|
||||||
|
|
||||||
|
### What to Expect
|
||||||
|
- **Response time**: Within 48 hours
|
||||||
|
- **Updates**: Regular communication about the issue
|
||||||
|
- **Fix timeline**: Depends on severity and complexity
|
||||||
|
- **Disclosure**: Coordinated disclosure after fix is deployed
|
||||||
|
|
||||||
|
### Please Do Not
|
||||||
|
- Create public issues for security vulnerabilities
|
||||||
|
- Exploit vulnerabilities beyond proof-of-concept
|
||||||
|
- Access data that doesn't belong to you
|
||||||
|
- Perform DoS attacks
|
||||||
|
|
||||||
|
## Security Measures
|
||||||
|
|
||||||
|
This project implements several security best practices:
|
||||||
|
|
||||||
|
### Code Security
|
||||||
|
- **Input Sanitization**: All user inputs are sanitized (minimal as site is static)
|
||||||
|
- **Dependencies**: Regular security audits via `npm audit`
|
||||||
|
- **Linting**: ESLint configured to catch common security issues
|
||||||
|
- **CSP Ready**: Content Security Policy headers can be added by hosting provider
|
||||||
|
|
||||||
|
### Build Security
|
||||||
|
- **Dependency Scanning**: GitHub Dependabot enabled
|
||||||
|
- **Automated Updates**: Security patches applied automatically
|
||||||
|
- **CI/CD**: All code passes linting and build checks
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- No sensitive data in repository
|
||||||
|
- No API keys or credentials in code
|
||||||
|
- HTTPS enforced on live site
|
||||||
|
- Regular dependency updates
|
||||||
|
- Minimal external dependencies
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
As a static personal website:
|
||||||
|
- No backend or database
|
||||||
|
- No user authentication
|
||||||
|
- No data collection or storage
|
||||||
|
- Minimal attack surface
|
||||||
|
|
||||||
|
## Security Updates
|
||||||
|
|
||||||
|
Security fixes are released as soon as possible after discovery. Check the [CHANGELOG](CHANGELOG.md) for security-related updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: November 2024*
|
||||||
@@ -22,6 +22,7 @@ export default [
|
|||||||
setTimeout: 'readonly',
|
setTimeout: 'readonly',
|
||||||
setInterval: 'readonly',
|
setInterval: 'readonly',
|
||||||
clearInterval: 'readonly',
|
clearInterval: 'readonly',
|
||||||
|
clearTimeout: 'readonly',
|
||||||
IntersectionObserver: 'readonly',
|
IntersectionObserver: 'readonly',
|
||||||
__dirname: 'readonly',
|
__dirname: 'readonly',
|
||||||
},
|
},
|
||||||
@@ -33,6 +34,19 @@ export default [
|
|||||||
'no-var': 'warn',
|
'no-var': 'warn',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['tests/**/*.js'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
global: 'writable',
|
||||||
|
beforeEach: 'readonly',
|
||||||
|
describe: 'readonly',
|
||||||
|
it: 'readonly',
|
||||||
|
expect: 'readonly',
|
||||||
|
vi: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
'node_modules/**',
|
'node_modules/**',
|
||||||
|
|||||||
999
package-lock.json
generated
999
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,10 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:ui": "vitest --ui",
|
||||||
|
"test:run": "vitest run",
|
||||||
|
"coverage": "vitest run --coverage",
|
||||||
"lint": "eslint . --ext .js,.html",
|
"lint": "eslint . --ext .js,.html",
|
||||||
"lint:fix": "eslint . --ext .js,.html --fix",
|
"lint:fix": "eslint . --ext .js,.html --fix",
|
||||||
"format": "prettier --write \"**/*.{js,html,css,json,md}\"",
|
"format": "prettier --write \"**/*.{js,html,css,json,md}\"",
|
||||||
@@ -28,14 +32,17 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://zane.org",
|
"homepage": "https://zane.org",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vitest/ui": "^4.0.8",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-html": "^8.1.3",
|
"eslint-plugin-html": "^8.1.3",
|
||||||
"imagemin": "^9.0.1",
|
"imagemin": "^9.0.1",
|
||||||
"imagemin-mozjpeg": "^10.0.0",
|
"imagemin-mozjpeg": "^10.0.0",
|
||||||
"imagemin-pngquant": "^10.0.0",
|
"imagemin-pngquant": "^10.0.0",
|
||||||
|
"jsdom": "^27.1.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"terser": "^5.44.1",
|
"terser": "^5.44.1",
|
||||||
"vite": "^7.2.2"
|
"vite": "^7.2.2",
|
||||||
|
"vitest": "^4.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
tests/animations.test.js
Normal file
40
tests/animations.test.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { typeWriter } from '../assets/js/modules/animations.js';
|
||||||
|
|
||||||
|
describe('Animations Module', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('typeWriter', () => {
|
||||||
|
it('should type text character by character', async () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
const text = 'Hello';
|
||||||
|
typeWriter(element, text, 50);
|
||||||
|
|
||||||
|
// Wait for first setTimeout to execute
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
|
||||||
|
// Complete text should be shown
|
||||||
|
expect(element.innerHTML).toBe('Hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null element gracefully', () => {
|
||||||
|
expect(() => {
|
||||||
|
typeWriter(null, 'text', 50);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty text', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
typeWriter(element, '', 50);
|
||||||
|
vi.advanceTimersByTime(100);
|
||||||
|
|
||||||
|
expect(element.innerHTML).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
69
tests/dom.test.js
Normal file
69
tests/dom.test.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { DOM, getNavElements, getMainElements } from '../assets/js/modules/dom.js';
|
||||||
|
|
||||||
|
describe('DOM Module', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Setup basic HTML structure
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<div class="container">
|
||||||
|
<div class="avatar">Avatar</div>
|
||||||
|
<h1 class="brand-name">ZaneDev</h1>
|
||||||
|
<p class="tagline">Tagline</p>
|
||||||
|
<nav class="navigation">
|
||||||
|
<a href="#" class="nav-button">Button 1</a>
|
||||||
|
<a href="#" class="nav-button">Button 2</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DOM utility', () => {
|
||||||
|
it('should cache and retrieve elements', () => {
|
||||||
|
const avatar = DOM.get('.avatar');
|
||||||
|
expect(avatar).toBeTruthy();
|
||||||
|
expect(avatar.textContent).toBe('Avatar');
|
||||||
|
|
||||||
|
// Should return cached version on second call
|
||||||
|
const avatarAgain = DOM.get('.avatar');
|
||||||
|
expect(avatarAgain).toBe(avatar);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get all elements matching selector', () => {
|
||||||
|
const buttons = DOM.getAll('.nav-button');
|
||||||
|
expect(buttons).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear cache', () => {
|
||||||
|
DOM.get('.avatar');
|
||||||
|
expect(Object.keys(DOM.cache).length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
DOM.clearCache();
|
||||||
|
expect(Object.keys(DOM.cache).length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getMainElements', () => {
|
||||||
|
it('should return main UI elements', () => {
|
||||||
|
const elements = getMainElements();
|
||||||
|
|
||||||
|
expect(elements.avatar).toBeTruthy();
|
||||||
|
expect(elements.brandName).toBeTruthy();
|
||||||
|
expect(elements.tagline).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNavElements', () => {
|
||||||
|
it('should return navigation elements', () => {
|
||||||
|
// Add required elements for navigation
|
||||||
|
document.body.innerHTML += `
|
||||||
|
<div class="content-section"></div>
|
||||||
|
<button class="close-button">Close</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const elements = getNavElements();
|
||||||
|
|
||||||
|
expect(elements.navButtons).toBeTruthy();
|
||||||
|
expect(elements.navButtons.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
34
tests/setup.js
Normal file
34
tests/setup.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Test setup file
|
||||||
|
import { beforeEach, vi } from 'vitest';
|
||||||
|
|
||||||
|
// Mock Web Audio API
|
||||||
|
global.AudioContext = vi.fn().mockImplementation(() => ({
|
||||||
|
createOscillator: vi.fn().mockReturnValue({
|
||||||
|
connect: vi.fn(),
|
||||||
|
frequency: {
|
||||||
|
setValueAtTime: vi.fn(),
|
||||||
|
exponentialRampToValueAtTime: vi.fn(),
|
||||||
|
},
|
||||||
|
start: vi.fn(),
|
||||||
|
stop: vi.fn(),
|
||||||
|
}),
|
||||||
|
createGain: vi.fn().mockReturnValue({
|
||||||
|
connect: vi.fn(),
|
||||||
|
gain: {
|
||||||
|
setValueAtTime: vi.fn(),
|
||||||
|
exponentialRampToValueAtTime: vi.fn(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
destination: {},
|
||||||
|
currentTime: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock requestAnimationFrame
|
||||||
|
global.requestAnimationFrame = vi.fn((callback) => setTimeout(callback, 16));
|
||||||
|
global.cancelAnimationFrame = vi.fn(clearTimeout);
|
||||||
|
|
||||||
|
// Reset DOM before each test
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
document.head.innerHTML = '';
|
||||||
|
});
|
||||||
@@ -37,4 +37,13 @@ export default defineConfig({
|
|||||||
port: 5173,
|
port: 5173,
|
||||||
open: true,
|
open: true,
|
||||||
},
|
},
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: './tests/setup.js',
|
||||||
|
coverage: {
|
||||||
|
reporter: ['text', 'html'],
|
||||||
|
exclude: ['node_modules/', 'dist/', 'tests/'],
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user