Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to GitHub Devwatch will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.2] - 2025-11-19

### Fixed
- Fixed test failures in popup repository controller due to Chrome storage API mocks using callback-based API instead of promise-based
- Fixed lint warnings for unused variables in test files

### Changed
- Expanded test coverage from ~40% to 47% line coverage
- Added comprehensive tests for background service worker message handlers
- Added tests for rate limiting and storage quota handling
- Added tests for badge expiry filtering
- Added tests for token validation edge cases
- Added animation timing tests using Jest fake timers
- Updated Jest coverage thresholds and collection patterns

### Added
- New test files for notification manager, theme controller, activity list view, and repository list view

## [1.0.1] - 2025-11-19

### Fixed
Expand Down
10 changes: 7 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ export default {
testEnvironment: 'jsdom',
testMatch: ['**/tests/**/*.test.js'],
collectCoverageFrom: [
'background.js',
'popup/*.js',
'popup/controllers/*.js',
'popup/views/*.js',
'options/*.js',
'options/controllers/*.js',
'options/views/*.js',
'shared/*.js',
'shared/api/*.js',
'shared/ui/*.js',
'!**/*.test.js'
],
coverageThreshold: {
global: {
branches: 45,
functions: 40,
lines: 50
branches: 46,
functions: 44,
lines: 47
}
},
transform: {},
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "GitHub Devwatch",
"version": "1.0.0",
"version": "1.0.2",
"description": "Monitor pull requests, issues, and releases across multiple GitHub repositories. Get notifications and never miss activity.",
"author": "Jonathan Martinez",
"permissions": [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "github-devwatch-chrome",
"version": "1.0.0",
"version": "1.0.2",
"description": "Chrome extension for GitHub Devwatch",
"type": "module",
"scripts": {
Expand Down
55 changes: 37 additions & 18 deletions shared/ui/notification-manager.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { escapeHtml } from '../sanitize.js';

/**
* Toast Notification Manager
* Singleton class for managing toast notifications across the application
* Note: Using textContent for text insertion provides automatic XSS protection
*/
class NotificationManager {
constructor() {
Expand Down Expand Up @@ -67,30 +66,50 @@ class NotificationManager {

const icon = this.getIcon(type);

let toastHTML = `
<div class="toast-icon">${icon}</div>
<div class="toast-message">${escapeHtml(message)}</div>
<button class="toast-close" aria-label="Close toast">✕</button>
<div class="toast-progress"></div>
`;
// Build toast structure
const toastIcon = document.createElement('div');
toastIcon.className = 'toast-icon';
toastIcon.textContent = icon;

const toastMessage = document.createElement('div');
toastMessage.className = 'toast-message';
toastMessage.textContent = message;

const closeBtn = document.createElement('button');
closeBtn.className = 'toast-close';
closeBtn.setAttribute('aria-label', 'Close toast');
closeBtn.textContent = '✕';

const progressBar = document.createElement('div');
progressBar.className = 'toast-progress';

// Append elements in order
toast.appendChild(toastIcon);
toast.appendChild(toastMessage);

// Add action button if provided
if (action) {
const actionButton = `<button class="toast-action" data-action="${action.id}">${escapeHtml(action.text)}</button>`;
toastHTML = toastHTML.replace('</div><button class="toast-close">', `</div>${actionButton}<button class="toast-close">`);
const actionBtn = document.createElement('button');
actionBtn.className = 'toast-action';
actionBtn.setAttribute('data-action', action.id);
actionBtn.textContent = action.text;
toast.appendChild(actionBtn);
}

toast.innerHTML = toastHTML;
toast.appendChild(closeBtn);
toast.appendChild(progressBar);

// Add event listeners
const closeBtn = toast.querySelector('.toast-close');
closeBtn.addEventListener('click', () => this.remove(id));

const actionBtn = toast.querySelector('.toast-action');
if (actionBtn && action) {
actionBtn.addEventListener('click', () => {
action.handler();
this.remove(id);
});
if (action) {
const actionBtn = toast.querySelector('.toast-action');
if (actionBtn) {
actionBtn.addEventListener('click', () => {
action.handler();
this.remove(id);
});
}
}

return toast;
Expand Down
Loading
Loading