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 ]
|
||||
|
||||
jobs:
|
||||
lint-and-build:
|
||||
lint-test-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
@@ -33,6 +33,9 @@ jobs:
|
||||
- name: Check formatting
|
||||
run: npm run format:check
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test:run
|
||||
|
||||
- name: Build project
|
||||
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',
|
||||
setInterval: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
IntersectionObserver: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
},
|
||||
@@ -33,6 +34,19 @@ export default [
|
||||
'no-var': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tests/**/*.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
global: 'writable',
|
||||
beforeEach: 'readonly',
|
||||
describe: 'readonly',
|
||||
it: 'readonly',
|
||||
expect: 'readonly',
|
||||
vi: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'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",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:run": "vitest run",
|
||||
"coverage": "vitest run --coverage",
|
||||
"lint": "eslint . --ext .js,.html",
|
||||
"lint:fix": "eslint . --ext .js,.html --fix",
|
||||
"format": "prettier --write \"**/*.{js,html,css,json,md}\"",
|
||||
@@ -28,14 +32,17 @@
|
||||
},
|
||||
"homepage": "https://zane.org",
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^4.0.8",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-html": "^8.1.3",
|
||||
"imagemin": "^9.0.1",
|
||||
"imagemin-mozjpeg": "^10.0.0",
|
||||
"imagemin-pngquant": "^10.0.0",
|
||||
"jsdom": "^27.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"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,
|
||||
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