docs: Add comprehensive toast notification test and documentation

Test Addition:
- Created tests/mjs/29-pdf-toast-notifications.test.mjs (22 assertions)
- Validates toast elements, JavaScript API, animations
- Tests both PDF download scenarios (modal stays open vs. closes early)
- Verifies toast lifecycle: prepare → progress → success → dismiss
- Tests icon/title/message updates and progress bar animation

Test Coverage:
 Toast DOM elements exist
 JavaScript functions available (showPDFToast, hidePDFToast)
 Manual toast trigger and auto-hide
 PDF modal integration - Scenario A (stay in modal)
 PDF modal integration - Scenario B (close modal, toast appears)
 Toast content updates (📥)
 Progress bar animation
 CSS animations defined (toastSlideIn, toastLifecycle, progressShrink)

Documentation Addition:
- Created doc/13-TOAST-NOTIFICATIONS.md (comprehensive guide)
- Architecture and component structure
- User experience flows (2 scenarios documented)
- JavaScript API reference (showPDFToast, hidePDFToast, showError)
- CSS structure and animations
- Template structure with ARIA accessibility
- PDF download integration details
- Accessibility features and testing guide
- Customization guide and troubleshooting

Documentation Updates:
- Updated doc/README.md (added entry #13, total: 12 → 13 docs)
- Updated tests/README.md (test count: 27 → 28)
- Updated tests/TEST-SUMMARY.md (added toast notifications coverage)
- Updated tests/mjs/README.md (added test 29 description)

Test Count: 28 active tests
Doc Count: 13 core documents
Coverage: Complete UX feedback system with dual-mode operation
This commit is contained in:
juanatsap
2025-11-20 13:05:46 +00:00
parent fb1c781a20
commit 9c5f0b20d8
6 changed files with 1215 additions and 7 deletions
+792
View File
@@ -0,0 +1,792 @@
# Toast Notification System
**Comprehensive documentation for the CV project's toast notification system.**
---
## Table of Contents
1. [Overview](#overview)
2. [Architecture](#architecture)
3. [User Experience Flows](#user-experience-flows)
4. [Implementation Details](#implementation-details)
5. [JavaScript API](#javascript-api)
6. [CSS Structure](#css-structure)
7. [Template Structure](#template-structure)
8. [Integration with PDF Downloads](#integration-with-pdf-downloads)
9. [Accessibility](#accessibility)
10. [Testing](#testing)
11. [Customization Guide](#customization-guide)
12. [Troubleshooting](#troubleshooting)
---
## Overview
The toast notification system provides non-intrusive visual feedback for asynchronous operations, particularly PDF generation and downloads. It enhances user experience by:
- **Immediate Feedback**: Users know their action was registered
- **Progress Tracking**: Visual indication of operation duration
- **Completion Confirmation**: Clear success/error messaging
- **Non-Blocking UX**: Users can continue working while operations complete
### Key Features
✅ Multiple toast variants (success, error, info, warning)
✅ Animated slide-in/slide-out
✅ Progress bar with timing animation
✅ Auto-dismiss with configurable duration
✅ Manual close button
✅ Mobile responsive (bottom-center on small screens)
✅ Accessibility compliant (ARIA labels, reduced motion support)
✅ Bilingual support (English/Spanish)
---
## Architecture
### Component Structure
```
Toast System
├── CSS Layer (static/css/04-interactive/_toasts.css)
│ ├── Toast container positioning
│ ├── Variant styles (success, error, info, warning)
│ ├── Animations (slide, fade, progress)
│ └── Responsive breakpoints
├── HTML Templates (templates/partials/widgets/)
│ ├── error-toast.html (Error notifications)
│ └── pdf-toast.html (PDF download feedback)
├── JavaScript API (static/js/main.js)
│ ├── showPDFToast(options)
│ ├── hidePDFToast()
│ └── showError(message)
└── Integration Layer
└── PDF Modal (templates/partials/modals/pdf-modal.html)
└── downloadPDF() function
```
### Design Principles
1. **Progressive Enhancement**: Works without JavaScript (fallback to modal only)
2. **Separation of Concerns**: CSS handles animations, JS handles logic
3. **Reusability**: Generic toast system extensible for any notification type
4. **Performance**: Minimal DOM manipulation, CSS-driven animations
5. **Accessibility**: Screen reader announcements, keyboard navigation
---
## User Experience Flows
### Flow 1: PDF Download (Modal Stays Open)
```
User clicks "Download PDF"
Modal shows loading overlay
"⏳ Preparing PDF... (~4 seconds)"
User waits in modal
PDF downloads to browser
Modal auto-closes
DONE ✅
```
**When to use:** Default behavior for users who want to watch progress.
### Flow 2: PDF Download (User Closes Modal)
```
User clicks "Download PDF"
Modal shows loading overlay
User presses ESC or clicks X
Toast slides in (bottom-right)
"📥 Preparing PDF..."
"Generating Default CV (5 pages)... (~4s)"
[Progress bar animates]
After estimated time (~4s)
Toast updates
"✅ PDF Ready!"
"Check your downloads folder"
Toast auto-dismisses (3s)
DONE ✅
```
**When to use:** Power users who want to continue working while PDF generates.
---
## Implementation Details
### File Structure
```
cv/
├── static/css/04-interactive/
│ └── _toasts.css # Toast styles (242 lines)
├── templates/partials/widgets/
│ ├── error-toast.html # Error notification template
│ └── pdf-toast.html # PDF download notification template
├── templates/index.html # Includes toast templates
├── static/js/main.js # Toast functions
└── templates/partials/modals/
└── pdf-modal.html # Integrates toast with downloads
```
### CSS Organization
**File:** `static/css/04-interactive/_toasts.css`
**Sections:**
1. **Base Toast Container** - Positioning, layout, common styles
2. **Toast Variants** - Color schemes for different notification types
3. **Content Elements** - Icon, title, message, close button
4. **Progress Bar** - Visual countdown indicator
5. **Animations** - Slide-in, slide-out, lifecycle, progress
6. **Responsive Design** - Mobile breakpoints
7. **Accessibility** - Reduced motion preferences
8. **Print Styles** - Hide toasts in print media
---
## JavaScript API
### `showPDFToast(options)`
Display a PDF download toast notification.
**Parameters:**
```javascript
{
icon: string, // Emoji icon (📥, ✅, ⚠️)
title: string, // Toast title
message: string, // Toast message
duration: number, // Auto-hide duration in ms (default: 5000)
autoHide: boolean // Auto-dismiss after duration (default: true)
}
```
**Example:**
```javascript
window.showPDFToast({
icon: '📥',
title: 'Preparing PDF...',
message: 'Generating Default CV (5 pages)... (~4s)',
duration: 4000,
autoHide: false // Manually control dismissal
});
```
**Return:** None
**Side Effects:**
- Adds `.show` class to toast element
- Starts progress bar animation
- Schedules auto-hide timeout (if `autoHide: true`)
---
### `hidePDFToast()`
Immediately hide the PDF toast.
**Parameters:** None
**Example:**
```javascript
window.hidePDFToast();
```
**Return:** None
**Side Effects:**
- Removes `.show` class from toast element
- Cancels progress bar animation
---
### `showError(message)`
Display an error toast notification.
**Parameters:**
```javascript
message: string // Error message to display
```
**Example:**
```javascript
window.showError('Failed to load content. Please try again.');
```
**Return:** None
**Side Effects:**
- Shows error toast with red styling
- Auto-hides after 5 seconds (CSS animation)
---
## CSS Structure
### Toast Variants
#### Success Toast (Green)
```css
.success-toast {
background: linear-gradient(135deg,
rgba(40, 167, 69, 0.95) 0%,
rgba(25, 135, 84, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
#### Error Toast (Red)
```css
.error-toast {
background: linear-gradient(135deg,
rgba(220, 53, 69, 0.95) 0%,
rgba(200, 35, 51, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
#### Info Toast (Blue)
```css
.info-toast {
background: linear-gradient(135deg,
rgba(13, 110, 253, 0.95) 0%,
rgba(10, 88, 202, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
#### Warning Toast (Yellow)
```css
.warning-toast {
background: linear-gradient(135deg,
rgba(255, 193, 7, 0.95) 0%,
rgba(255, 167, 38, 0.95) 100%);
color: #333;
border-left: 4px solid #333;
}
```
### Key Animations
#### Slide In
```css
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%) translateY(0);
}
to {
opacity: 1;
transform: translateX(0) translateY(0);
}
}
```
#### Lifecycle (Auto-hide)
```css
@keyframes toastLifecycle {
0% {
opacity: 1;
transform: translateX(0) translateY(0);
}
85% {
opacity: 1;
transform: translateX(0) translateY(0);
}
100% {
opacity: 0;
transform: translateX(100%) translateY(0);
}
}
```
#### Progress Bar
```css
@keyframes progressShrink {
from { width: 100%; }
to { width: 0%; }
}
```
---
## Template Structure
### PDF Toast Template
**File:** `templates/partials/widgets/pdf-toast.html`
```html
<div id="pdf-toast" class="success-toast no-print"
role="status" aria-live="polite" aria-atomic="true">
<!-- Icon -->
<span class="toast-icon" id="pdf-toast-icon">📥</span>
<!-- Content -->
<div class="toast-content">
<p class="toast-title" id="pdf-toast-title">Preparing PDF</p>
<p class="toast-message" id="pdf-toast-message"></p>
</div>
<!-- Close Button -->
<button aria-label="Close notification"
class="toast-close"
onclick="document.getElementById('pdf-toast').classList.remove('show')">
×
</button>
<!-- Progress Bar -->
<div class="toast-progress">
<div class="toast-progress-bar" id="pdf-toast-progress"></div>
</div>
</div>
```
### Key Elements
- **`role="status"`**: ARIA role for status messages
- **`aria-live="polite"`**: Screen reader announcement (non-interrupting)
- **`aria-atomic="true"`**: Read entire toast as single unit
- **`.no-print`**: Hidden in print media
- **`#pdf-toast-icon`**: Dynamically updated emoji
- **`#pdf-toast-title`**: Main heading
- **`#pdf-toast-message`**: Detailed message
- **`.toast-progress-bar`**: Visual countdown indicator
---
## Integration with PDF Downloads
### Download Flow Integration
**File:** `templates/partials/modals/pdf-modal.html`
```javascript
function downloadPDF() {
// ... setup code ...
// Track if modal is closed by user
let modalClosedByUser = false;
const onModalClose = () => {
modalClosedByUser = true;
// Show toast when user closes modal
if (window.showPDFToast) {
window.showPDFToast({
icon: '📥',
title: isSpanish ? 'Preparando PDF...' : 'Preparing PDF...',
message: `Generating ${formatName}... (~${estimatedTime}s)`,
duration: estimatedTime * 1000,
autoHide: false
});
}
};
// Listen for modal close
modal.addEventListener('close', onModalClose, { once: true });
// Trigger download
window.location.href = url;
// After estimated time: update toast or close modal
setTimeout(() => {
if (modalClosedByUser && window.showPDFToast) {
// Update toast to success
window.showPDFToast({
icon: '✅',
title: isSpanish ? '¡PDF Listo!' : 'PDF Ready!',
message: isSpanish
? 'Revisa tu carpeta de descargas'
: 'Check your downloads folder',
duration: 3000,
autoHide: true
});
} else {
// Close modal if still open
modal.close();
}
}, estimatedTime * 1000);
}
```
### Format-Specific Timing
| PDF Format | Pages | Estimated Time | Icon Progression |
|------------|-------|----------------|------------------|
| Short | 4 | 3 seconds | 📥 → ✅ |
| Default | 5 | 4 seconds | 📥 → ✅ |
| Extended | 9 | 8 seconds | 📥 → ✅ |
---
## Accessibility
### ARIA Attributes
```html
<div id="pdf-toast"
role="status" <!-- Status message role -->
aria-live="polite" <!-- Non-interrupting announcement -->
aria-atomic="true"> <!-- Read as single unit -->
```
### Screen Reader Behavior
1. **Toast Appears**: Screen reader announces title + message
2. **Toast Updates**: New content announced automatically
3. **Toast Dismisses**: Silent (user already informed)
### Keyboard Navigation
- **Close Button**: Focusable with `Tab`
- **Enter/Space**: Activate close button
- **ESC**: Closes parent modal (which may trigger toast)
### Reduced Motion
Users with motion sensitivity get simplified animations:
```css
@media (prefers-reduced-motion: reduce) {
.success-toast,
.error-toast,
.toast {
animation: none;
opacity: 1;
}
.toast-progress-bar {
animation: none;
}
}
```
**Effect:** Toast appears/disappears instantly without sliding/fading.
---
## Testing
### Automated Test
**File:** `tests/mjs/29-pdf-toast-notifications.test.mjs`
**Coverage:**
- ✅ Toast elements exist in DOM
- ✅ Toast initially hidden
- ✅ JavaScript functions available
- ✅ Manual toast trigger works
- ✅ Toast auto-hides after duration
- ✅ PDF modal integration (Scenario A: Stay in modal)
- ✅ PDF modal integration (Scenario B: Close modal early)
- ✅ CSS animations defined
- ✅ Icon/title/message updates
- ✅ Progress bar animation
**Run Test:**
```bash
bun tests/mjs/29-pdf-toast-notifications.test.mjs
```
### Manual Testing Checklist
**Scenario 1: Modal Overlay Only**
- [ ] Open PDF modal
- [ ] Select Default CV
- [ ] Click Download PDF
- [ ] Wait in modal without closing
- [ ] Verify overlay shows with spinner
- [ ] Verify modal closes after ~4 seconds
- [ ] Verify NO toast appears
**Scenario 2: Toast After Modal Close**
- [ ] Open PDF modal
- [ ] Select Extended CV (9 pages)
- [ ] Click Download PDF
- [ ] Immediately press ESC to close modal
- [ ] Verify toast appears bottom-right
- [ ] Verify icon is 📥
- [ ] Verify title says "Preparing PDF..."
- [ ] Verify message includes format name and time
- [ ] Verify progress bar animates (shrinks)
- [ ] Wait ~8 seconds
- [ ] Verify toast updates to ✅
- [ ] Verify title says "PDF Ready!"
- [ ] Verify toast auto-dismisses after ~3 seconds
**Scenario 3: Close Button**
- [ ] Trigger toast manually (browser console): `showPDFToast({icon: '🧪', title: 'Test', message: 'Test message'})`
- [ ] Click × close button
- [ ] Verify toast disappears immediately
**Scenario 4: Mobile Responsive**
- [ ] Resize browser to < 540px width
- [ ] Trigger toast
- [ ] Verify toast is full-width at bottom
- [ ] Verify content is readable
- [ ] Verify close button accessible
---
## Customization Guide
### Adding a New Toast Variant
**1. Define CSS Class** (`static/css/04-interactive/_toasts.css`):
```css
.custom-toast {
background: linear-gradient(135deg,
rgba(156, 39, 176, 0.95) 0%,
rgba(123, 31, 162, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
```
**2. Create HTML Template** (`templates/partials/widgets/custom-toast.html`):
```html
{{define "custom-toast"}}
<div id="custom-toast" class="custom-toast no-print"
role="status" aria-live="polite">
<span class="toast-icon" id="custom-toast-icon">🎨</span>
<div class="toast-content">
<p class="toast-title" id="custom-toast-title">Custom Title</p>
<p class="toast-message" id="custom-toast-message"></p>
</div>
<button class="toast-close">×</button>
</div>
{{end}}
```
**3. Add JavaScript Function** (`static/js/main.js`):
```javascript
window.showCustomToast = function(options = {}) {
const toast = document.getElementById('custom-toast');
const icon = document.getElementById('custom-toast-icon');
const title = document.getElementById('custom-toast-title');
const message = document.getElementById('custom-toast-message');
if (options.icon) icon.textContent = options.icon;
if (options.title) title.textContent = options.title;
if (options.message) message.textContent = options.message;
toast.classList.remove('show');
void toast.offsetWidth;
toast.classList.add('show');
if (options.autoHide !== false) {
const duration = options.duration || 5000;
setTimeout(() => toast.classList.remove('show'), duration);
}
};
```
**4. Include in Index** (`templates/index.html`):
```html
{{template "custom-toast" .}}
```
### Changing Animation Duration
**CSS** (`_toasts.css`):
```css
/* Change lifecycle duration (default: 5s) */
.toast.show {
animation: toastLifecycle 8s ease forwards; /* Now 8 seconds */
}
/* Change progress bar duration */
.toast-progress-bar {
animation: progressShrink 8s linear forwards; /* Match lifecycle */
}
```
**JavaScript** (when calling function):
```javascript
window.showPDFToast({
icon: '📥',
title: 'Preparing...',
message: 'This will take longer',
duration: 10000 // 10 seconds
});
```
---
## Troubleshooting
### Toast Not Appearing
**Symptom:** Calling `showPDFToast()` has no effect.
**Checks:**
1. Verify toast element exists: `document.getElementById('pdf-toast')`
2. Check console for JavaScript errors
3. Verify CSS is loaded: Inspect element and check for `.success-toast` styles
4. Confirm `toasts.css` imported in `main.css`
5. Check `index.html` includes `{{template "pdf-toast" .}}`
**Solution:**
```bash
# Verify CSS import
grep "toasts.css" static/css/main.css
# Verify template inclusion
grep "pdf-toast" templates/index.html
# Check browser console
# Should see no errors when calling showPDFToast()
```
---
### Toast Not Auto-Dismissing
**Symptom:** Toast appears but never disappears.
**Checks:**
1. Verify `autoHide` is not set to `false`
2. Check CSS animation: `toastLifecycle` should be defined
3. Inspect element: `animation` property should be set
4. Check for `prefers-reduced-motion` (disables animations)
**Solution:**
```javascript
// Explicitly set autoHide
window.showPDFToast({
icon: '✅',
title: 'Test',
message: 'Should auto-hide',
duration: 3000,
autoHide: true // Explicitly enable
});
// Or manually hide
setTimeout(() => window.hidePDFToast(), 3000);
```
---
### Progress Bar Not Animating
**Symptom:** Progress bar appears but doesn't shrink.
**Checks:**
1. Verify `progressShrink` animation is defined in CSS
2. Check animation is applied: Inspect `#pdf-toast-progress`
3. Confirm `prefers-reduced-motion` is not active
4. Check `duration` parameter matches animation duration
**Solution:**
```javascript
// Ensure progress bar animation resets
const progressBar = document.getElementById('pdf-toast-progress');
if (progressBar) {
progressBar.style.animation = 'none';
void progressBar.offsetWidth; // Trigger reflow
progressBar.style.animation = 'progressShrink 5000ms linear forwards';
}
```
---
### Toast Overlaps Content on Mobile
**Symptom:** Toast covers important content on small screens.
**Checks:**
1. Verify responsive CSS is loaded
2. Check viewport width: `< 540px` should trigger mobile styles
3. Inspect `z-index` conflicts
**Solution:**
```css
/* Adjust mobile positioning */
@media (max-width: 540px) {
.toast {
bottom: 1rem; /* Lower if needed */
right: 1rem;
left: 1rem;
z-index: 10000; /* Ensure on top */
}
}
```
---
### Multiple Toasts Stacking
**Symptom:** Calling `showPDFToast()` multiple times creates overlapping toasts.
**Root Cause:** Current system uses single toast element, so multiple calls update the same toast.
**Expected Behavior:** Later calls replace earlier toast content.
**If You Need Multiple Toasts:**
1. Clone toast element before showing:
```javascript
function showMultipleToasts(options) {
const original = document.getElementById('pdf-toast');
const clone = original.cloneNode(true);
clone.id = `toast-${Date.now()}`;
document.body.appendChild(clone);
// Update clone content and show
// ... similar logic to showPDFToast ...
}
```
2. Or use a toast queue system (advanced).
---
## Related Documentation
- [2. Modern Web Techniques](2-MODERN-WEB-TECHNIQUES.md) - HTMX patterns and hypermedia architecture
- [12. CSS Architecture](12-CSS-ARCHITECTURE.md) - Modular CSS structure and ITCSS organization
- [3. API Reference](3-API.md) - Backend API endpoints including PDF export
- [11. PDF Export](11-PDF-EXPORT.md) - PDF generation with chromedp
---
**Last Updated**: 2025-11-20
**Version**: 1.0
**Status**: Production Ready
**Test Coverage**: 29-pdf-toast-notifications.test.mjs (22 assertions)
+3 -1
View File
@@ -17,6 +17,7 @@
- [4. Hyperscript Rules](4-HYPERSCRIPT-RULES.md) - Hyperscript conventions and best practices
- [5. Zoom Implementation](5-ZOOM-IMPLEMENTATION.md) - Custom zoom feature technical details
- [12. CSS Architecture](12-CSS-ARCHITECTURE.md) - Modular CSS structure and ITCSS organization ⭐
- [13. Toast Notifications](13-TOAST-NOTIFICATIONS.md) - Toast notification system for PDF downloads and user feedback
**Deployment & Operations**
- [8. Deployment Guide](8-DEPLOYMENT.md) - Production deployment instructions
@@ -44,6 +45,7 @@
| 4 | [HYPERSCRIPT-RULES.md](4-HYPERSCRIPT-RULES.md) | Hyperscript coding conventions | Frontend developers |
| 5 | [ZOOM_IMPLEMENTATION.md](5-ZOOM-IMPLEMENTATION.md) | Zoom feature implementation details | Feature developers |
| 12 | [CSS-ARCHITECTURE.md](12-CSS-ARCHITECTURE.md) | Modular CSS structure, ITCSS layers, HTMX integration | Frontend developers, designers |
| 13 | [TOAST-NOTIFICATIONS.md](13-TOAST-NOTIFICATIONS.md) | Toast notification system, PDF download feedback, user notifications | Frontend developers, UX designers |
### User & Operations Documentation
@@ -139,4 +141,4 @@ All documentation in this project follows these standards:
**Last Updated**: 2025-11-20
**Documentation Status**: ✅ Clean, organized, zero redundancy
**Total Active Docs**: 12 core documents + archive
**Total Active Docs**: 13 core documents + archive
+1 -1
View File
@@ -295,6 +295,6 @@ Every time something breaks in production:
---
**Last Updated**: 2025-11-20
**Test Count**: 27 active tests (0-28 numbered + migration tests)
**Test Count**: 28 active tests (0-29 numbered + migration tests)
**Status**: Production specification
**Responsibility**: These tests define what "working" means
+2 -2
View File
@@ -287,8 +287,8 @@ When adding tests:
---
**Last Updated**: 2025-11-20
**Test Count**: 27 active tests - Comprehensive coverage
**Coverage**: Complete (UI, keyboard, HTMX, i18n, modals, mobile, zoom, hover-sync, hyperscript, skeleton loaders, color themes, button positioning, PDF modal, icons, dark theme, course icons, references)
**Test Count**: 28 active tests - Comprehensive coverage
**Coverage**: Complete (UI, keyboard, HTMX, i18n, modals, mobile, zoom, hover-sync, hyperscript, skeleton loaders, color themes, button positioning, PDF modal, icons, dark theme, course icons, references, toast notifications)
**Status**: SINGLE SOURCE OF TRUTH - Production specification
**Philosophy**: Each test validates specific functionality and edge cases
@@ -0,0 +1,403 @@
#!/usr/bin/env bun
/**
* PDF TOAST NOTIFICATIONS TEST
* =============================
* Tests the toast notification system for PDF downloads
* Validates both modal overlay and toast notification flows
*/
import { chromium } from 'playwright';
const URL = "http://localhost:1999";
async function testPDFToastNotifications() {
console.log('🧪 PDF TOAST NOTIFICATIONS TEST\n');
console.log('='.repeat(70));
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
const errors = [];
const testResults = [];
// Track console errors
page.on('console', msg => {
if (msg.type() === 'error') {
errors.push(msg.text());
console.log(`❌ CONSOLE ERROR: ${msg.text()}`);
}
});
try {
// =====================================================================
// SETUP: Navigate to page
// =====================================================================
console.log('\n📍 STEP 1: Navigate to CV page');
await page.goto(URL);
await page.waitForLoadState('networkidle');
console.log('✅ Page loaded successfully');
// =====================================================================
// TEST 1: Toast Elements Exist
// =====================================================================
console.log('\n📍 STEP 2: Verify toast elements exist in DOM');
const pdfToast = await page.locator('#pdf-toast');
const toastExists = await pdfToast.count() > 0;
testResults.push({
test: 'PDF toast element exists',
passed: toastExists
});
console.log(toastExists ? '✅ PDF toast element found' : '❌ PDF toast element not found');
const toastIcon = await page.locator('#pdf-toast-icon');
const iconExists = await toastIcon.count() > 0;
testResults.push({
test: 'Toast icon element exists',
passed: iconExists
});
console.log(iconExists ? '✅ Toast icon element found' : '❌ Toast icon not found');
const toastTitle = await page.locator('#pdf-toast-title');
const titleExists = await toastTitle.count() > 0;
testResults.push({
test: 'Toast title element exists',
passed: titleExists
});
console.log(titleExists ? '✅ Toast title element found' : '❌ Toast title not found');
const toastMessage = await page.locator('#pdf-toast-message');
const messageExists = await toastMessage.count() > 0;
testResults.push({
test: 'Toast message element exists',
passed: messageExists
});
console.log(messageExists ? '✅ Toast message element found' : '❌ Toast message not found');
const progressBar = await page.locator('#pdf-toast-progress');
const progressExists = await progressBar.count() > 0;
testResults.push({
test: 'Toast progress bar exists',
passed: progressExists
});
console.log(progressExists ? '✅ Toast progress bar found' : '❌ Progress bar not found');
// =====================================================================
// TEST 2: Toast Initially Hidden
// =====================================================================
console.log('\n📍 STEP 3: Verify toast is initially hidden');
const isInitiallyHidden = !(await pdfToast.evaluate(el => el.classList.contains('show')));
testResults.push({
test: 'Toast initially hidden',
passed: isInitiallyHidden
});
console.log(isInitiallyHidden ? '✅ Toast is hidden initially' : '❌ Toast is visible (should be hidden)');
// =====================================================================
// TEST 3: JavaScript Functions Available
// =====================================================================
console.log('\n📍 STEP 4: Verify JavaScript toast functions exist');
const showPDFToastExists = await page.evaluate(() => typeof window.showPDFToast === 'function');
testResults.push({
test: 'showPDFToast() function exists',
passed: showPDFToastExists
});
console.log(showPDFToastExists ? '✅ showPDFToast() function available' : '❌ showPDFToast() not found');
const hidePDFToastExists = await page.evaluate(() => typeof window.hidePDFToast === 'function');
testResults.push({
test: 'hidePDFToast() function exists',
passed: hidePDFToastExists
});
console.log(hidePDFToastExists ? '✅ hidePDFToast() function available' : '❌ hidePDFToast() not found');
// =====================================================================
// TEST 4: Manual Toast Trigger Test
// =====================================================================
console.log('\n📍 STEP 5: Test manual toast trigger');
await page.evaluate(() => {
window.showPDFToast({
icon: '🧪',
title: 'Test Toast',
message: 'This is a test notification',
duration: 3000
});
});
await page.waitForTimeout(500);
const isToastVisible = await pdfToast.evaluate(el => el.classList.contains('show'));
testResults.push({
test: 'Toast appears when showPDFToast() called',
passed: isToastVisible
});
console.log(isToastVisible ? '✅ Toast appears correctly' : '❌ Toast did not appear');
const toastIconText = await toastIcon.textContent();
const iconCorrect = toastIconText === '🧪';
testResults.push({
test: 'Toast icon updates correctly',
passed: iconCorrect
});
console.log(iconCorrect ? `✅ Toast icon: "${toastIconText}"` : `❌ Icon incorrect: "${toastIconText}"`);
const toastTitleText = await toastTitle.textContent();
const titleCorrect = toastTitleText === 'Test Toast';
testResults.push({
test: 'Toast title updates correctly',
passed: titleCorrect
});
console.log(titleCorrect ? `✅ Toast title: "${toastTitleText}"` : `❌ Title incorrect: "${toastTitleText}"`);
// Wait for auto-hide
console.log('⏳ Waiting 3.5 seconds for toast auto-hide...');
await page.waitForTimeout(3500);
const isToastHidden = !(await pdfToast.evaluate(el => el.classList.contains('show')));
testResults.push({
test: 'Toast auto-hides after duration',
passed: isToastHidden
});
console.log(isToastHidden ? '✅ Toast auto-hid correctly' : '❌ Toast still visible (should auto-hide)');
// =====================================================================
// TEST 5: PDF Modal Integration - Scenario A (Stay in Modal)
// =====================================================================
console.log('\n📍 STEP 6: Test PDF modal integration (Scenario A: Stay in Modal)');
// Open PDF modal
const pdfButton = await page.locator('button[onclick*="openPdfModal"], .download-btn').first();
await pdfButton.click();
await page.waitForTimeout(500);
const modal = await page.locator('#pdf-modal[open]');
const isModalOpen = await modal.count() > 0;
testResults.push({
test: 'PDF modal opens',
passed: isModalOpen
});
console.log(isModalOpen ? '✅ PDF modal opened' : '❌ PDF modal did not open');
// Select Default CV
const defaultCard = await page.locator('.pdf-option-card[data-cv-format="default"]');
await defaultCard.click();
await page.waitForTimeout(300);
// Click download but DON'T close modal
const downloadBtn = await page.locator('#pdf-download-btn');
await downloadBtn.click();
await page.waitForTimeout(300);
// Check overlay appears
const overlay = await page.locator('#pdf-loading-overlay');
const isOverlayActive = await overlay.evaluate(el => el.classList.contains('active'));
testResults.push({
test: 'Loading overlay appears on download',
passed: isOverlayActive
});
console.log(isOverlayActive ? '✅ Loading overlay is active' : '❌ Loading overlay did not activate');
// Toast should NOT appear yet (modal still open)
await page.waitForTimeout(500);
const toastShouldNotShow = !(await pdfToast.evaluate(el => el.classList.contains('show')));
testResults.push({
test: 'Toast does not appear when modal stays open',
passed: toastShouldNotShow
});
console.log(toastShouldNotShow ? '✅ Toast correctly hidden (modal open)' : '⚠️ Toast appeared (should wait for modal close)');
// Wait for modal to close automatically
console.log('⏳ Waiting 5 seconds for modal auto-close...');
await page.waitForTimeout(5000);
const isModalClosed = await page.locator('#pdf-modal[open]').count() === 0;
testResults.push({
test: 'Modal auto-closes after download',
passed: isModalClosed
});
console.log(isModalClosed ? '✅ Modal closed automatically' : '❌ Modal still open (should auto-close)');
// =====================================================================
// TEST 6: PDF Modal Integration - Scenario B (Close Modal Early)
// =====================================================================
console.log('\n📍 STEP 7: Test PDF modal integration (Scenario B: Close Modal Early)');
// Re-open PDF modal
await pdfButton.click();
await page.waitForTimeout(500);
// Select Short CV (faster for testing)
const shortCard = await page.locator('.pdf-option-card[data-cv-format="short"]');
await shortCard.click();
await page.waitForTimeout(300);
// Click download
await downloadBtn.click();
await page.waitForTimeout(300);
// Immediately close modal (ESC key)
await page.keyboard.press('Escape');
await page.waitForTimeout(500);
// Toast SHOULD appear now
const toastAppearsOnClose = await pdfToast.evaluate(el => el.classList.contains('show'));
testResults.push({
test: 'Toast appears when modal closed early',
passed: toastAppearsOnClose
});
console.log(toastAppearsOnClose ? '✅ Toast appeared after modal close' : '❌ Toast did not appear');
if (toastAppearsOnClose) {
// Check toast content
const currentIcon = await toastIcon.textContent();
const iconIsPreparing = currentIcon === '📥';
testResults.push({
test: 'Toast shows preparing icon (📥)',
passed: iconIsPreparing
});
console.log(iconIsPreparing ? `✅ Toast icon: "${currentIcon}"` : `⚠️ Unexpected icon: "${currentIcon}"`);
const currentTitle = await toastTitle.textContent();
const titleIsPreparing = currentTitle.includes('Preparing') || currentTitle.includes('Preparando');
testResults.push({
test: 'Toast shows preparing title',
passed: titleIsPreparing
});
console.log(titleIsPreparing ? `✅ Toast title: "${currentTitle}"` : `⚠️ Title: "${currentTitle}"`);
// Check progress bar animation
const hasProgressAnimation = await page.evaluate(() => {
const bar = document.getElementById('pdf-toast-progress');
const style = window.getComputedStyle(bar);
return style.animation && style.animation !== 'none';
});
testResults.push({
test: 'Progress bar animates',
passed: hasProgressAnimation
});
console.log(hasProgressAnimation ? '✅ Progress bar is animating' : '❌ Progress bar not animated');
// Wait for toast to update to success
console.log('⏳ Waiting 4 seconds for toast to update to success...');
await page.waitForTimeout(4000);
const finalIcon = await toastIcon.textContent();
const iconIsSuccess = finalIcon === '✅';
testResults.push({
test: 'Toast updates to success icon (✅)',
passed: iconIsSuccess
});
console.log(iconIsSuccess ? `✅ Toast updated: "${finalIcon}"` : `⚠️ Final icon: "${finalIcon}"`);
const finalTitle = await toastTitle.textContent();
const titleIsReady = finalTitle.includes('Ready') || finalTitle.includes('Listo');
testResults.push({
test: 'Toast shows ready title',
passed: titleIsReady
});
console.log(titleIsReady ? `✅ Toast title: "${finalTitle}"` : `⚠️ Title: "${finalTitle}"`);
// Wait for final auto-dismiss
console.log('⏳ Waiting 4 seconds for final toast auto-dismiss...');
await page.waitForTimeout(4000);
const finallyHidden = !(await pdfToast.evaluate(el => el.classList.contains('show')));
testResults.push({
test: 'Toast auto-dismisses after success',
passed: finallyHidden
});
console.log(finallyHidden ? '✅ Toast dismissed successfully' : '⚠️ Toast still visible');
}
// =====================================================================
// TEST 7: CSS Animations
// =====================================================================
console.log('\n📍 STEP 8: Verify CSS animations are defined');
const hasSlideInAnimation = await page.evaluate(() => {
const sheet = Array.from(document.styleSheets).find(s => s.href?.includes('toasts.css'));
if (!sheet) return false;
const rules = Array.from(sheet.cssRules || []);
return rules.some(r => r.name === 'toastSlideIn');
});
testResults.push({
test: 'toastSlideIn animation defined',
passed: hasSlideInAnimation
});
console.log(hasSlideInAnimation ? '✅ toastSlideIn animation exists' : '❌ Animation not found');
const hasLifecycleAnimation = await page.evaluate(() => {
const sheet = Array.from(document.styleSheets).find(s => s.href?.includes('toasts.css'));
if (!sheet) return false;
const rules = Array.from(sheet.cssRules || []);
return rules.some(r => r.name === 'toastLifecycle');
});
testResults.push({
test: 'toastLifecycle animation defined',
passed: hasLifecycleAnimation
});
console.log(hasLifecycleAnimation ? '✅ toastLifecycle animation exists' : '❌ Animation not found');
const hasProgressAnimation = await page.evaluate(() => {
const sheet = Array.from(document.styleSheets).find(s => s.href?.includes('toasts.css'));
if (!sheet) return false;
const rules = Array.from(sheet.cssRules || []);
return rules.some(r => r.name === 'progressShrink');
});
testResults.push({
test: 'progressShrink animation defined',
passed: hasProgressAnimation
});
console.log(hasProgressAnimation ? '✅ progressShrink animation exists' : '❌ Animation not found');
} catch (error) {
console.error('\n❌ Test execution error:', error.message);
errors.push(error.message);
}
// =====================================================================
// SUMMARY
// =====================================================================
console.log("\n" + "=".repeat(70));
console.log("📊 TEST SUMMARY\n");
const passedTests = testResults.filter(t => t.passed).length;
const totalTests = testResults.length;
testResults.forEach(result => {
const icon = result.passed ? '✅' : '❌';
console.log(`${icon} ${result.test}`);
});
console.log(`\n📈 Results: ${passedTests}/${totalTests} tests passed`);
if (errors.length > 0) {
console.log(`\n⚠️ ${errors.length} console errors detected`);
errors.forEach(err => console.log(` - ${err}`));
}
if (passedTests === totalTests && errors.length === 0) {
console.log('\n🎉 ALL TESTS PASSED! Toast notification system works perfectly!');
} else if (passedTests >= totalTests * 0.9) {
console.log('\n✅ MOST TESTS PASSED! Minor issues detected.');
} else {
console.log('\n⚠️ SOME TESTS FAILED - Review needed');
}
console.log("\n" + "=".repeat(70));
console.log("\n💡 Browser will stay open for manual inspection.");
console.log(" You can:");
console.log(" 1. Test both scenarios manually");
console.log(" 2. Try different PDF formats (Short, Default, Long)");
console.log(" 3. Test close button on toast");
console.log(" 4. Check responsive behavior (resize window)");
console.log(" 5. Verify progress bar animations");
console.log("\nPress Ctrl+C when done.\n");
await new Promise(() => {}); // Keep browser open
}
await testPDFToastNotifications();
+14 -3
View File
@@ -1,6 +1,6 @@
# CV Project Test Suite (Active Tests)
**27 comprehensive tests** covering all functionality from UI interactions to PDF generation.
**28 comprehensive tests** covering all functionality from UI interactions to PDF generation.
## Quick Start
@@ -92,6 +92,17 @@ Final course icon validation
### 28-references-pdf-download.test.mjs
References section PDF download button
### 29-pdf-toast-notifications.test.mjs
Toast notification system for PDF downloads
- Toast elements exist in DOM
- JavaScript functions available (showPDFToast, hidePDFToast)
- Manual toast trigger and auto-hide
- PDF modal integration - Scenario A (Stay in modal)
- PDF modal integration - Scenario B (Close modal early, toast appears)
- Toast content updates (preparing → success)
- Progress bar animation
- CSS animations defined (toastSlideIn, toastLifecycle, progressShrink)
## Migration Tests
### test-preference-migration.test.mjs
@@ -139,6 +150,6 @@ bun tests/mjs/28-references-pdf-download.test.mjs
---
**Last Updated**: 2025-11-20
**Test Count**: 27 active tests
**Coverage**: Complete functionality coverage
**Test Count**: 28 active tests
**Coverage**: Complete functionality coverage including toast notifications
**Status**: Production specification