/** * Integration test suite for clipboard functionality across different scenarios * Tests end-to-end workflows combining clipboard operations with various settings */ // Mock Chrome APIs global.chrome = { storage: { sync: { get: jest.fn(), set: jest.fn() } }, runtime: { sendMessage: jest.fn() }, tabs: { query: jest.fn(), sendMessage: jest.fn() } }; // Mock DOM APIs global.document = { createElement: jest.fn(() => ({ style: {}, classList: { add: jest.fn(), remove: jest.fn() }, appendChild: jest.fn(), click: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), innerHTML: '', textContent: '', id: '', className: '' })), head: { appendChild: jest.fn() }, body: { appendChild: jest.fn() }, addEventListener: jest.fn(), removeEventListener: jest.fn(), querySelectorAll: jest.fn(() => []), getElementById: jest.fn() }; global.window = { getComputedStyle: jest.fn(() => ({})), devicePixelRatio: 2, isSecureContext: true, CSS: { escape: jest.fn(str => str) }, setTimeout: jest.fn((fn, delay) => { if (delay === 0) fn(); return 1; }) }; // Mock html2canvas global.html2canvas = jest.fn(); describe('Integration Scenarios - Clipboard with Different Backgrounds', () => { let screenshotSelector; let mockElement; let mockCanvas; beforeEach(() => { jest.clearAllMocks(); // Reset clipboard mock for each test if (!global.navigator) { global.navigator = {}; } global.navigator.clipboard = { write: jest.fn().mockResolvedValue() }; global.window.ClipboardItem = jest.fn().mockImplementation((data) => { return { data, type: 'image/png' }; }); global.window.isSecureContext = true; // Mock element to capture mockElement = { classList: { add: jest.fn(), remove: jest.fn() }, style: {}, getBoundingClientRect: () => ({ left: 0, top: 0, width: 100, height: 100 }), scrollHeight: 100, clientHeight: 100, scrollWidth: 100, clientWidth: 100, children: [], parentNode: { children: [] } }; // Mock canvas mockCanvas = { toBlob: jest.fn((callback, format) => { const mockBlob = new Blob(['mock image data'], { type: format }); Object.defineProperty(mockBlob, 'size', { value: 1024 }); // 1KB callback(mockBlob); }), toDataURL: jest.fn(() => 'data:image/png;base64,mockdata') }; global.html2canvas = jest.fn().mockResolvedValue(mockCanvas); // Import ScreenshotSelector (simplified for testing) const ScreenshotSelector = class { constructor() { this.isActive = false; this.background = 'black'; this.copyToClipboard = true; this.saveToPc = true; this.overlay = null; } async init(background = 'black', copyToClipboard = true, saveToPc = true) { this.background = background; this.copyToClipboard = copyToClipboard; this.saveToPc = saveToPc; this.isActive = true; this.createUI(); } createUI() { this.overlay = global.document.createElement('div'); global.document.body.appendChild(this.overlay); } checkClipboardAPIAvailability() { if (!global.navigator.clipboard) { return { available: false, reason: 'Clipboard API not available in this browser' }; } if (!global.window.ClipboardItem) { return { available: false, reason: 'ClipboardItem not supported in this browser' }; } if (!global.window.isSecureContext) { return { available: false, reason: 'Clipboard API requires a secure context (HTTPS)' }; } if (!global.navigator.clipboard.write) { return { available: false, reason: 'Clipboard write functionality not available' }; } return { available: true }; } async copyCanvasToClipboard(canvas) { try { const availability = this.checkClipboardAPIAvailability(); if (!availability.available) { return { success: false, error: availability.reason, errorType: 'api_unavailable' }; } const blob = await new Promise((resolve, reject) => { canvas.toBlob((blob) => { if (blob) resolve(blob); else reject(new Error('Canvas to blob conversion returned null')); }, 'image/png'); }); const maxBlobSize = 50 * 1024 * 1024; if (blob.size > maxBlobSize) { return { success: false, error: `Image too large for clipboard (${Math.round(blob.size / 1024 / 1024)}MB)`, errorType: 'size_limit' }; } const clipboardItem = new global.window.ClipboardItem({ 'image/png': blob }); await global.navigator.clipboard.write([clipboardItem]); return { success: true }; } catch (error) { if (error.name === 'NotAllowedError') { return { success: false, error: 'Permission denied', errorType: 'permission_denied' }; } return { success: false, error: error.message, errorType: 'unexpected' }; } } async captureElement(element) { try { // Generate canvas with background-specific settings const canvas = await global.html2canvas(element, { backgroundColor: this.background === 'transparent' ? null : this.background === 'white' ? '#ffffff' : '#000000', useCORS: true, allowTaint: false }); // Always download first const link = global.document.createElement('a'); link.href = canvas.toDataURL('image/png'); link.download = `screenshot-${new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-')}.png`; link.click(); // Handle clipboard if enabled let clipboardResult = null; if (this.copyToClipboard) { clipboardResult = await this.copyCanvasToClipboard(canvas); } // Update UI based on results (matching new message format) let successMessage = 'Screenshot captured!'; let messageIcon = '✅'; if (this.saveToPc && this.copyToClipboard) { // Both operations enabled if (clipboardResult && clipboardResult.success) { successMessage = 'Screenshot saved and copied to clipboard!'; messageIcon = '✅'; } else { // Save succeeded but clipboard failed const errorMessage = clipboardResult ? clipboardResult.error.toLowerCase() : 'clipboard copy failed'; successMessage = `Screenshot saved successfully! However, ${errorMessage}`; messageIcon = '⚠️'; } } else if (this.saveToPc && !this.copyToClipboard) { // Only save to PC enabled successMessage = 'Screenshot saved to downloads folder!'; messageIcon = '💾'; } else if (!this.saveToPc && this.copyToClipboard) { // Only clipboard enabled if (clipboardResult && clipboardResult.success) { successMessage = 'Screenshot copied to clipboard!'; messageIcon = '📋'; } else { // Clipboard failed - this is a complete failure since no other output method is enabled const errorMessage = clipboardResult ? clipboardResult.error : 'clipboard copy failed'; successMessage = `Screenshot capture failed: ${errorMessage}`; messageIcon = '❌'; } } this.overlay.innerHTML = `
${successMessage}
`; return { success: true, downloadSuccess: true, clipboardResult: clipboardResult, message: successMessage }; } catch (error) { this.overlay.innerHTML = `
Screenshot failed: ${error.message}
`; return { success: false, error: error.message }; } } }; screenshotSelector = new ScreenshotSelector(); }); test('should capture with transparent background and copy to clipboard', async () => { await screenshotSelector.init('transparent', true); const result = await screenshotSelector.captureElement(mockElement); expect(global.html2canvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({ backgroundColor: null, // Transparent background useCORS: true, allowTaint: false })); expect(result.success).toBe(true); expect(result.downloadSuccess).toBe(true); expect(result.clipboardResult.success).toBe(true); expect(result.message).toBe('Screenshot saved and copied to clipboard!'); }); test('should capture with black background and copy to clipboard', async () => { await screenshotSelector.init('black', true); const result = await screenshotSelector.captureElement(mockElement); expect(global.html2canvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({ backgroundColor: '#000000', // Black background useCORS: true, allowTaint: false })); expect(result.success).toBe(true); expect(result.clipboardResult.success).toBe(true); expect(result.message).toBe('Screenshot saved and copied to clipboard!'); }); test('should capture with white background and copy to clipboard', async () => { await screenshotSelector.init('white', true); const result = await screenshotSelector.captureElement(mockElement); expect(global.html2canvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({ backgroundColor: '#ffffff', // White background useCORS: true, allowTaint: false })); expect(result.success).toBe(true); expect(result.clipboardResult.success).toBe(true); expect(result.message).toBe('Screenshot saved and copied to clipboard!'); }); test('should capture without clipboard when disabled', async () => { await screenshotSelector.init('black', false, true); // saveToPc = true, copyToClipboard = false const result = await screenshotSelector.captureElement(mockElement); expect(result.success).toBe(true); expect(result.downloadSuccess).toBe(true); expect(result.clipboardResult).toBe(null); // No clipboard operation attempted expect(result.message).toBe('Screenshot saved to downloads folder!'); if (global.navigator.clipboard?.write) { expect(global.navigator.clipboard.write).not.toHaveBeenCalled(); } }); test('should handle clipboard failure gracefully while maintaining download', async () => { // Reset and mock clipboard failure global.navigator.clipboard = { write: jest.fn().mockRejectedValue(new DOMException('Permission denied', 'NotAllowedError')) }; await screenshotSelector.init('black', true); const result = await screenshotSelector.captureElement(mockElement); expect(result.success).toBe(true); expect(result.downloadSuccess).toBe(true); expect(result.clipboardResult.success).toBe(false); expect(result.clipboardResult.errorType).toBe('permission_denied'); expect(result.message).toContain('Screenshot saved successfully!'); expect(result.message).toContain('permission denied'); }); test('should handle clipboard API unavailable scenario', async () => { // Mock clipboard API not available global.navigator.clipboard = undefined; await screenshotSelector.init('transparent', true); const result = await screenshotSelector.captureElement(mockElement); expect(result.success).toBe(true); expect(result.downloadSuccess).toBe(true); expect(result.clipboardResult.success).toBe(false); expect(result.clipboardResult.errorType).toBe('api_unavailable'); expect(result.message).toContain('Screenshot saved successfully!'); expect(result.message).toContain('clipboard'); }); }); describe('Integration Scenarios - Complete Workflow', () => { let mockPopupElements; beforeEach(() => { jest.clearAllMocks(); // Mock popup elements mockPopupElements = { clipboardToggle: { checked: true, addEventListener: jest.fn() }, backgroundSelect: { value: 'black', addEventListener: jest.fn() }, startButton: { addEventListener: jest.fn(), style: { display: 'block' } }, status: { textContent: '', className: '', style: { display: 'none' } } }; global.document.getElementById = jest.fn((id) => { switch (id) { case 'clipboard-toggle': return mockPopupElements.clipboardToggle; case 'background-select': return mockPopupElements.backgroundSelect; case 'start-selector': return mockPopupElements.startButton; case 'status': return mockPopupElements.status; default: return null; } }); // Mock Chrome APIs global.chrome.storage.sync.get.mockResolvedValue({ backgroundPreference: 'black', copyToClipboard: true }); global.chrome.tabs.query.mockResolvedValue([{ id: 123, url: 'https://example.com' }]); global.chrome.tabs.sendMessage.mockResolvedValue(); }); test('should complete full workflow from popup to content script', async () => { let startButtonCallback; mockPopupElements.startButton.addEventListener = jest.fn((event, callback) => { if (event === 'click') startButtonCallback = callback; }); // Simulate popup initialization const popupScript = ` document.addEventListener('DOMContentLoaded', async () => { const backgroundSelect = document.getElementById('background-select'); const clipboardToggle = document.getElementById('clipboard-toggle'); const startBtn = document.getElementById('start-selector'); // Load preferences const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard']); if (saved.backgroundPreference) { backgroundSelect.value = saved.backgroundPreference; } clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true; // Start selector startBtn.addEventListener('click', async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); await chrome.tabs.sendMessage(tab.id, { action: 'startSelector', background: backgroundSelect.value, copyToClipboard: clipboardToggle.checked }); }); }); `; // Execute popup initialization await new Promise(resolve => { global.document.addEventListener = jest.fn((event, callback) => { if (event === 'DOMContentLoaded') { callback(); resolve(); } }); eval(popupScript); }); // Verify preferences were loaded expect(mockPopupElements.backgroundSelect.value).toBe('black'); expect(mockPopupElements.clipboardToggle.checked).toBe(true); // Simulate start button click await startButtonCallback(); // Verify message was sent to content script with correct parameters expect(global.chrome.tabs.sendMessage).toHaveBeenCalledWith(123, { action: 'startSelector', background: 'black', copyToClipboard: true }); }); test('should handle preference changes and persist them', async () => { let clipboardChangeCallback; let backgroundChangeCallback; mockPopupElements.clipboardToggle.addEventListener = jest.fn((event, callback) => { if (event === 'change') clipboardChangeCallback = callback; }); mockPopupElements.backgroundSelect.addEventListener = jest.fn((event, callback) => { if (event === 'change') backgroundChangeCallback = callback; }); // Initialize popup await new Promise(resolve => { global.document.addEventListener = jest.fn((event, callback) => { if (event === 'DOMContentLoaded') { callback(); resolve(); } }); const popupScript = ` document.addEventListener('DOMContentLoaded', async () => { const backgroundSelect = document.getElementById('background-select'); const clipboardToggle = document.getElementById('clipboard-toggle'); backgroundSelect.addEventListener('change', () => { chrome.storage.sync.set({ backgroundPreference: backgroundSelect.value }); }); clipboardToggle.addEventListener('change', () => { chrome.storage.sync.set({ copyToClipboard: clipboardToggle.checked }); }); }); `; eval(popupScript); }); // Change background preference mockPopupElements.backgroundSelect.value = 'transparent'; backgroundChangeCallback(); expect(global.chrome.storage.sync.set).toHaveBeenCalledWith({ backgroundPreference: 'transparent' }); // Change clipboard preference mockPopupElements.clipboardToggle.checked = false; clipboardChangeCallback(); expect(global.chrome.storage.sync.set).toHaveBeenCalledWith({ copyToClipboard: false }); }); test('should handle incompatible pages gracefully', async () => { // Mock incompatible page global.chrome.tabs.query.mockResolvedValue([{ id: 123, url: 'chrome://settings/' }]); let startButtonCallback; mockPopupElements.startButton.addEventListener = jest.fn((event, callback) => { if (event === 'click') startButtonCallback = callback; }); // Initialize popup with incompatible page handling const popupScript = ` document.addEventListener('DOMContentLoaded', async () => { const startBtn = document.getElementById('start-selector'); const status = document.getElementById('status'); startBtn.addEventListener('click', async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('edge://') || tab.url.startsWith('about:')) { status.textContent = 'Error: Cannot capture screenshots on this page type.'; status.className = 'status error'; status.style.display = 'block'; return; } // Normal flow would continue here }); }); `; await new Promise(resolve => { global.document.addEventListener = jest.fn((event, callback) => { if (event === 'DOMContentLoaded') { callback(); resolve(); } }); eval(popupScript); }); // Simulate start button click on incompatible page await startButtonCallback(); // Verify error message was shown expect(mockPopupElements.status.textContent).toBe('Error: Cannot capture screenshots on this page type.'); expect(mockPopupElements.status.className).toBe('status error'); expect(mockPopupElements.status.style.display).toBe('block'); // Verify no message was sent to content script expect(global.chrome.tabs.sendMessage).not.toHaveBeenCalled(); }); });