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:
copilot-swe-agent[bot]
2025-11-09 00:14:31 +00:00
parent 542d80802e
commit 7a4996c40a
10 changed files with 1426 additions and 3 deletions

View File

@@ -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
View 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
View 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*

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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
View 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
View 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 = '';
});

View File

@@ -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/'],
},
},
});