Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a018c36d67 |
8 changed files with 197 additions and 1557 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,4 +1,3 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
.kiro/
|
.kiro/
|
||||||
html-to-image/
|
.vscode/
|
||||||
.vscode/
|
|
||||||
178
content.js
178
content.js
|
|
@ -369,7 +369,7 @@ class ScreenshotSelector {
|
||||||
try {
|
try {
|
||||||
// Determine loading message based on enabled operations
|
// Determine loading message based on enabled operations
|
||||||
let loadingMessage = 'Capturing screenshot...';
|
let loadingMessage = 'Capturing screenshot...';
|
||||||
|
|
||||||
if (this.saveToPc && this.copyToClipboard) {
|
if (this.saveToPc && this.copyToClipboard) {
|
||||||
// Both operations enabled
|
// Both operations enabled
|
||||||
const availability = this.checkClipboardAPIAvailability();
|
const availability = this.checkClipboardAPIAvailability();
|
||||||
|
|
@ -411,42 +411,90 @@ class ScreenshotSelector {
|
||||||
element.classList.remove('screenshot-highlight');
|
element.classList.remove('screenshot-highlight');
|
||||||
this.removeScrollableIndicators();
|
this.removeScrollableIndicators();
|
||||||
|
|
||||||
|
// Store original styles
|
||||||
|
const originalStyles = {
|
||||||
|
overflow: element.style.overflow,
|
||||||
|
overflowY: element.style.overflowY,
|
||||||
|
overflowX: element.style.overflowX,
|
||||||
|
height: element.style.height,
|
||||||
|
maxHeight: element.style.maxHeight,
|
||||||
|
position: element.style.position,
|
||||||
|
zIndex: element.style.zIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Temporarily modify element for full capture
|
||||||
|
element.style.overflow = 'visible';
|
||||||
|
element.style.overflowY = 'visible';
|
||||||
|
element.style.overflowX = 'visible';
|
||||||
|
element.style.height = `${element.scrollHeight}px`;
|
||||||
|
element.style.maxHeight = 'none';
|
||||||
|
|
||||||
// Wait for fonts and external resources to load
|
// Wait for fonts and external resources to load
|
||||||
await this.waitForFontsAndResources(element);
|
await this.waitForFontsAndResources(element);
|
||||||
|
|
||||||
if (!window.htmlToImage || typeof window.htmlToImage.toCanvas !== 'function') {
|
// Enhanced html2canvas configuration
|
||||||
throw new Error('Screenshot renderer not available: htmlToImage.toCanvas() missing');
|
const canvas = await html2canvas(element, {
|
||||||
}
|
useCORS: true,
|
||||||
|
allowTaint: false,
|
||||||
|
backgroundColor: this.background === 'transparent' ? null :
|
||||||
|
this.background === 'white' ? '#ffffff' : '#000000',
|
||||||
|
|
||||||
const backgroundColor =
|
// Improved rendering options
|
||||||
this.background === 'transparent'
|
scale: window.devicePixelRatio || 1, // Use device pixel ratio for crisp images
|
||||||
? undefined
|
logging: false, // Disable logging for cleaner console
|
||||||
: this.background === 'white'
|
|
||||||
? '#ffffff'
|
|
||||||
: '#000000';
|
|
||||||
|
|
||||||
// Use html-to-image to render the element to a canvas.
|
// Better handling of external resources
|
||||||
// Important: avoid mutating the live DOM during capture (it can cause reflow/layout drift).
|
imageTimeout: 15000, // Wait longer for images to load
|
||||||
const canvas = await window.htmlToImage.toCanvas(element, {
|
|
||||||
backgroundColor,
|
// Font handling and style preservation
|
||||||
pixelRatio: window.devicePixelRatio || 1,
|
onclone: (clonedDoc, clonedElementParam) => {
|
||||||
// Brightspace/D2L and many sites have CORS-protected stylesheets/fonts which cause noisy,
|
// Ensure all stylesheets are loaded in cloned document
|
||||||
// expected DOMExceptions during CSS rule inspection. Keep logs clean by default.
|
const originalStyleSheets = Array.from(document.styleSheets);
|
||||||
logLevel: 'silent',
|
const clonedHead = clonedDoc.head;
|
||||||
// If the page heavily caches fonts/images, enabling this can help but may slow capture
|
|
||||||
cacheBust: false,
|
// Copy all stylesheets to cloned document
|
||||||
// Capture full scrollable area without reflowing the live element
|
originalStyleSheets.forEach(styleSheet => {
|
||||||
width: element.scrollWidth,
|
try {
|
||||||
height: element.scrollHeight,
|
if (styleSheet.href) {
|
||||||
style: {
|
// External stylesheet
|
||||||
overflow: 'visible',
|
const link = clonedDoc.createElement('link');
|
||||||
overflowX: 'visible',
|
link.rel = 'stylesheet';
|
||||||
overflowY: 'visible',
|
link.href = styleSheet.href;
|
||||||
height: `${element.scrollHeight}px`,
|
link.type = 'text/css';
|
||||||
maxHeight: 'none',
|
clonedHead.appendChild(link);
|
||||||
},
|
} else if (styleSheet.ownerNode && styleSheet.ownerNode.tagName === 'STYLE') {
|
||||||
|
// Inline stylesheet
|
||||||
|
const style = clonedDoc.createElement('style');
|
||||||
|
style.type = 'text/css';
|
||||||
|
try {
|
||||||
|
const cssText = Array.from(styleSheet.cssRules).map(rule => rule.cssText).join('\n');
|
||||||
|
style.textContent = cssText;
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to original text content
|
||||||
|
style.textContent = styleSheet.ownerNode.textContent;
|
||||||
|
}
|
||||||
|
clonedHead.appendChild(style);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Skip stylesheets that can't be accessed (CORS issues)
|
||||||
|
console.log('Skipped stylesheet due to CORS:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply computed styles to preserve appearance
|
||||||
|
const clonedElement = clonedElementParam;
|
||||||
|
const originalElement = element; // from outer scope
|
||||||
|
if (clonedElement && originalElement) {
|
||||||
|
this.preserveComputedStyles(clonedElement, originalElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clonedDoc;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore original styles
|
||||||
|
Object.assign(element.style, originalStyles);
|
||||||
|
|
||||||
// Conditionally download the image based on saveToPc preference
|
// Conditionally download the image based on saveToPc preference
|
||||||
if (this.saveToPc) {
|
if (this.saveToPc) {
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
|
|
@ -467,15 +515,15 @@ class ScreenshotSelector {
|
||||||
// Ensure clipboard errors don't break the workflow - this is a safety net
|
// Ensure clipboard errors don't break the workflow - this is a safety net
|
||||||
// The copyCanvasToClipboard method should handle all errors internally
|
// The copyCanvasToClipboard method should handle all errors internally
|
||||||
console.error('Unexpected clipboard error caught in captureElement:', error);
|
console.error('Unexpected clipboard error caught in captureElement:', error);
|
||||||
clipboardResult = {
|
clipboardResult = {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Unexpected clipboard error occurred',
|
error: 'Unexpected clipboard error occurred',
|
||||||
errorType: 'unexpected'
|
errorType: 'unexpected'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clipboardResult = {
|
clipboardResult = {
|
||||||
success: false,
|
success: false,
|
||||||
error: availability.reason,
|
error: availability.reason,
|
||||||
errorType: 'api_unavailable'
|
errorType: 'api_unavailable'
|
||||||
};
|
};
|
||||||
|
|
@ -486,7 +534,7 @@ class ScreenshotSelector {
|
||||||
let successMessage = 'Screenshot captured!';
|
let successMessage = 'Screenshot captured!';
|
||||||
let messageColor = 'rgba(39, 174, 96, 0.95)'; // Green for success
|
let messageColor = 'rgba(39, 174, 96, 0.95)'; // Green for success
|
||||||
let messageIcon = '✅';
|
let messageIcon = '✅';
|
||||||
|
|
||||||
// Determine success message based on enabled operations
|
// Determine success message based on enabled operations
|
||||||
if (this.saveToPc && this.copyToClipboard) {
|
if (this.saveToPc && this.copyToClipboard) {
|
||||||
// Both operations enabled
|
// Both operations enabled
|
||||||
|
|
@ -534,11 +582,11 @@ class ScreenshotSelector {
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Screenshot failed:', error);
|
console.error('Screenshot failed:', error);
|
||||||
|
|
||||||
// Provide specific error messages for screenshot failures based on enabled operations
|
// Provide specific error messages for screenshot failures based on enabled operations
|
||||||
let errorMessage = 'Screenshot capture failed. Please try again.';
|
let errorMessage = 'Screenshot capture failed. Please try again.';
|
||||||
let operationContext = '';
|
let operationContext = '';
|
||||||
|
|
||||||
// Add context about what operations were attempted
|
// Add context about what operations were attempted
|
||||||
if (this.saveToPc && this.copyToClipboard) {
|
if (this.saveToPc && this.copyToClipboard) {
|
||||||
operationContext = ' Neither file download nor clipboard copy could be completed.';
|
operationContext = ' Neither file download nor clipboard copy could be completed.';
|
||||||
|
|
@ -547,9 +595,9 @@ class ScreenshotSelector {
|
||||||
} else if (!this.saveToPc && this.copyToClipboard) {
|
} else if (!this.saveToPc && this.copyToClipboard) {
|
||||||
operationContext = ' Clipboard copy could not be completed.';
|
operationContext = ' Clipboard copy could not be completed.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.message) {
|
if (error.message) {
|
||||||
if (error.message.includes('htmlToImage') || error.message.includes('html-to-image')) {
|
if (error.message.includes('html2canvas')) {
|
||||||
errorMessage = `Screenshot rendering failed. Try selecting a different element or refresh the page.${operationContext}`;
|
errorMessage = `Screenshot rendering failed. Try selecting a different element or refresh the page.${operationContext}`;
|
||||||
} else if (error.message.includes('timeout')) {
|
} else if (error.message.includes('timeout')) {
|
||||||
errorMessage = `Screenshot capture timed out. Try selecting a smaller area or simpler element.${operationContext}`;
|
errorMessage = `Screenshot capture timed out. Try selecting a smaller area or simpler element.${operationContext}`;
|
||||||
|
|
@ -565,7 +613,7 @@ class ScreenshotSelector {
|
||||||
} else {
|
} else {
|
||||||
errorMessage = `Screenshot capture failed. Please try again.${operationContext}`;
|
errorMessage = `Screenshot capture failed. Please try again.${operationContext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.overlay.innerHTML = `
|
this.overlay.innerHTML = `
|
||||||
<div style="position: fixed; top: 20px; left: 20px; background: rgba(231, 76, 60, 0.95); color: white; padding: 12px 16px; border-radius: 8px; z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 14px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
|
<div style="position: fixed; top: 20px; left: 20px; background: rgba(231, 76, 60, 0.95); color: white; padding: 12px 16px; border-radius: 8px; z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 14px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
|
||||||
<div style="display: flex; align-items: center; gap: 10px;">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
|
|
@ -574,10 +622,10 @@ class ScreenshotSelector {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Notify popup about the failure
|
// Notify popup about the failure
|
||||||
chrome.runtime.sendMessage({ action: 'selectorStopped', reason: 'screenshot-failed' });
|
chrome.runtime.sendMessage({ action: 'selectorStopped', reason: 'screenshot-failed' });
|
||||||
|
|
||||||
setTimeout(() => this.cleanup(), 3000);
|
setTimeout(() => this.cleanup(), 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -630,7 +678,7 @@ class ScreenshotSelector {
|
||||||
*/
|
*/
|
||||||
getClipboardSupportFeedback() {
|
getClipboardSupportFeedback() {
|
||||||
const availability = this.checkClipboardAPIAvailability();
|
const availability = this.checkClipboardAPIAvailability();
|
||||||
|
|
||||||
if (availability.available) {
|
if (availability.available) {
|
||||||
return {
|
return {
|
||||||
supported: true,
|
supported: true,
|
||||||
|
|
@ -640,7 +688,7 @@ class ScreenshotSelector {
|
||||||
|
|
||||||
// Provide user-friendly messages for different scenarios
|
// Provide user-friendly messages for different scenarios
|
||||||
let userMessage = '';
|
let userMessage = '';
|
||||||
|
|
||||||
if (availability.reason.includes('not available') || availability.reason.includes('not supported')) {
|
if (availability.reason.includes('not available') || availability.reason.includes('not supported')) {
|
||||||
userMessage = 'Your browser doesn\'t support clipboard copying. Screenshots will still be downloaded.';
|
userMessage = 'Your browser doesn\'t support clipboard copying. Screenshots will still be downloaded.';
|
||||||
} else if (availability.reason.includes('secure context')) {
|
} else if (availability.reason.includes('secure context')) {
|
||||||
|
|
@ -675,39 +723,39 @@ class ScreenshotSelector {
|
||||||
switch (errorType) {
|
switch (errorType) {
|
||||||
case 'permission_denied':
|
case 'permission_denied':
|
||||||
return 'clipboard copy failed: permission denied. Please allow clipboard access in your browser settings';
|
return 'clipboard copy failed: permission denied. Please allow clipboard access in your browser settings';
|
||||||
|
|
||||||
case 'not_supported':
|
case 'not_supported':
|
||||||
case 'api_unavailable':
|
case 'api_unavailable':
|
||||||
return 'clipboard copy is not available in this browser';
|
return 'clipboard copy is not available in this browser';
|
||||||
|
|
||||||
case 'security_error':
|
case 'security_error':
|
||||||
return 'clipboard copy was blocked by browser security. Try using HTTPS or check site permissions';
|
return 'clipboard copy was blocked by browser security. Try using HTTPS or check site permissions';
|
||||||
|
|
||||||
case 'size_limit':
|
case 'size_limit':
|
||||||
case 'quota_exceeded':
|
case 'quota_exceeded':
|
||||||
return 'clipboard copy failed: image too large. Try capturing a smaller area';
|
return 'clipboard copy failed: image too large. Try capturing a smaller area';
|
||||||
|
|
||||||
case 'timeout':
|
case 'timeout':
|
||||||
return 'clipboard copy timed out. The image may be too large or complex';
|
return 'clipboard copy timed out. The image may be too large or complex';
|
||||||
|
|
||||||
case 'network_error':
|
case 'network_error':
|
||||||
return 'clipboard copy failed due to network error. Please try again';
|
return 'clipboard copy failed due to network error. Please try again';
|
||||||
|
|
||||||
case 'canvas_conversion':
|
case 'canvas_conversion':
|
||||||
return 'clipboard copy failed: unable to prepare image. Try capturing a different element';
|
return 'clipboard copy failed: unable to prepare image. Try capturing a different element';
|
||||||
|
|
||||||
case 'clipboard_item_creation':
|
case 'clipboard_item_creation':
|
||||||
return 'clipboard copy is not supported: your browser doesn\'t support image clipboard operations';
|
return 'clipboard copy is not supported: your browser doesn\'t support image clipboard operations';
|
||||||
|
|
||||||
case 'invalid_state':
|
case 'invalid_state':
|
||||||
return 'clipboard copy failed: browser clipboard is busy. Please try again';
|
return 'clipboard copy failed: browser clipboard is busy. Please try again';
|
||||||
|
|
||||||
case 'data_error':
|
case 'data_error':
|
||||||
return 'clipboard copy failed: invalid image data. Please try capturing again';
|
return 'clipboard copy failed: invalid image data. Please try capturing again';
|
||||||
|
|
||||||
case 'unexpected':
|
case 'unexpected':
|
||||||
return 'clipboard copy failed due to unexpected error';
|
return 'clipboard copy failed due to unexpected error';
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// For unknown error types, try to extract meaningful info from the error message
|
// For unknown error types, try to extract meaningful info from the error message
|
||||||
if (errorMessage.includes('permission') || errorMessage.includes('denied')) {
|
if (errorMessage.includes('permission') || errorMessage.includes('denied')) {
|
||||||
|
|
@ -734,8 +782,8 @@ class ScreenshotSelector {
|
||||||
// Check clipboard API availability first
|
// Check clipboard API availability first
|
||||||
const availability = this.checkClipboardAPIAvailability();
|
const availability = this.checkClipboardAPIAvailability();
|
||||||
if (!availability.available) {
|
if (!availability.available) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: availability.reason,
|
error: availability.reason,
|
||||||
errorType: 'api_unavailable'
|
errorType: 'api_unavailable'
|
||||||
};
|
};
|
||||||
|
|
@ -752,7 +800,7 @@ class ScreenshotSelector {
|
||||||
}
|
}
|
||||||
}, 'image/png');
|
}, 'image/png');
|
||||||
}),
|
}),
|
||||||
new Promise((_, reject) =>
|
new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Canvas to blob conversion timed out')), 10000)
|
setTimeout(() => reject(new Error('Canvas to blob conversion timed out')), 10000)
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
|
@ -784,7 +832,7 @@ class ScreenshotSelector {
|
||||||
// Write to clipboard with timeout
|
// Write to clipboard with timeout
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
navigator.clipboard.write([clipboardItem]),
|
navigator.clipboard.write([clipboardItem]),
|
||||||
new Promise((_, reject) =>
|
new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Clipboard write operation timed out')), 15000)
|
setTimeout(() => reject(new Error('Clipboard write operation timed out')), 15000)
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
|
@ -793,16 +841,16 @@ class ScreenshotSelector {
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Clipboard operation failed:', error);
|
console.error('Clipboard operation failed:', error);
|
||||||
|
|
||||||
// Comprehensive error handling with specific error types and user-friendly messages
|
// Comprehensive error handling with specific error types and user-friendly messages
|
||||||
let errorMessage = error.message || 'Unknown clipboard error';
|
let errorMessage = error.message || 'Unknown clipboard error';
|
||||||
let errorType = 'unknown';
|
let errorType = 'unknown';
|
||||||
|
|
||||||
// Permission-related errors
|
// Permission-related errors
|
||||||
if (error.name === 'NotAllowedError') {
|
if (error.name === 'NotAllowedError') {
|
||||||
errorMessage = 'Clipboard access denied. Please allow clipboard permissions in your browser settings.';
|
errorMessage = 'Clipboard access denied. Please allow clipboard permissions in your browser settings.';
|
||||||
errorType = 'permission_denied';
|
errorType = 'permission_denied';
|
||||||
}
|
}
|
||||||
// API support errors
|
// API support errors
|
||||||
else if (error.name === 'NotSupportedError') {
|
else if (error.name === 'NotSupportedError') {
|
||||||
errorMessage = 'Clipboard API not supported in this browser context.';
|
errorMessage = 'Clipboard API not supported in this browser context.';
|
||||||
|
|
@ -849,8 +897,8 @@ class ScreenshotSelector {
|
||||||
errorType = 'unknown';
|
errorType = 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
errorType: errorType
|
errorType: errorType
|
||||||
};
|
};
|
||||||
|
|
@ -915,7 +963,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
case 'checkClipboardSupport':
|
case 'checkClipboardSupport':
|
||||||
const availability = screenshotSelector.checkClipboardAPIAvailability();
|
const availability = screenshotSelector.checkClipboardAPIAvailability();
|
||||||
const feedback = screenshotSelector.getClipboardSupportFeedback();
|
const feedback = screenshotSelector.getClipboardSupportFeedback();
|
||||||
sendResponse({
|
sendResponse({
|
||||||
availability: availability,
|
availability: availability,
|
||||||
feedback: feedback
|
feedback: feedback
|
||||||
});
|
});
|
||||||
|
|
|
||||||
1389
html-to-image.js
1389
html-to-image.js
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -24,7 +24,7 @@
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["<all_urls>"],
|
"matches": ["<all_urls>"],
|
||||||
"js": ["html-to-image.js", "content.js"],
|
"js": ["html2canvas-pro.min.js", "content.js"],
|
||||||
"run_at": "document_idle"
|
"run_at": "document_idle"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,8 @@ global.window = {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock html-to-image
|
// Mock html2canvas
|
||||||
global.htmlToImage = {
|
global.html2canvas = jest.fn();
|
||||||
toCanvas: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
let screenshotSelector;
|
let screenshotSelector;
|
||||||
|
|
@ -65,7 +63,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Reset clipboard mock for each test
|
// Reset clipboard mock for each test
|
||||||
if (!global.navigator) {
|
if (!global.navigator) {
|
||||||
global.navigator = {};
|
global.navigator = {};
|
||||||
|
|
@ -77,7 +75,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
return { data, type: 'image/png' };
|
return { data, type: 'image/png' };
|
||||||
});
|
});
|
||||||
global.window.isSecureContext = true;
|
global.window.isSecureContext = true;
|
||||||
|
|
||||||
// Mock element to capture
|
// Mock element to capture
|
||||||
mockElement = {
|
mockElement = {
|
||||||
classList: {
|
classList: {
|
||||||
|
|
@ -104,7 +102,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
toDataURL: jest.fn(() => 'data:image/png;base64,mockdata')
|
toDataURL: jest.fn(() => 'data:image/png;base64,mockdata')
|
||||||
};
|
};
|
||||||
|
|
||||||
global.htmlToImage.toCanvas = jest.fn().mockResolvedValue(mockCanvas);
|
global.html2canvas = jest.fn().mockResolvedValue(mockCanvas);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -185,17 +183,11 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
async captureElement(element) {
|
async captureElement(element) {
|
||||||
try {
|
try {
|
||||||
// Generate canvas with background-specific settings
|
// Generate canvas with background-specific settings
|
||||||
const backgroundColor =
|
const canvas = await global.html2canvas(element, {
|
||||||
this.background === 'transparent'
|
backgroundColor: this.background === 'transparent' ? null :
|
||||||
? undefined
|
this.background === 'white' ? '#ffffff' : '#000000',
|
||||||
: this.background === 'white'
|
useCORS: true,
|
||||||
? '#ffffff'
|
allowTaint: false
|
||||||
: '#000000';
|
|
||||||
|
|
||||||
const canvas = await global.htmlToImage.toCanvas(element, {
|
|
||||||
backgroundColor,
|
|
||||||
pixelRatio: global.window.devicePixelRatio || 1,
|
|
||||||
cacheBust: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Always download first
|
// Always download first
|
||||||
|
|
@ -213,7 +205,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
// Update UI based on results (matching new message format)
|
// Update UI based on results (matching new message format)
|
||||||
let successMessage = 'Screenshot captured!';
|
let successMessage = 'Screenshot captured!';
|
||||||
let messageIcon = '✅';
|
let messageIcon = '✅';
|
||||||
|
|
||||||
if (this.saveToPc && this.copyToClipboard) {
|
if (this.saveToPc && this.copyToClipboard) {
|
||||||
// Both operations enabled
|
// Both operations enabled
|
||||||
if (clipboardResult && clipboardResult.success) {
|
if (clipboardResult && clipboardResult.success) {
|
||||||
|
|
@ -243,7 +235,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.overlay.innerHTML = `<div>${successMessage}</div>`;
|
this.overlay.innerHTML = `<div>${successMessage}</div>`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
downloadSuccess: true,
|
downloadSuccess: true,
|
||||||
|
|
@ -266,11 +258,13 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
|
|
||||||
test('should capture with transparent background and copy to clipboard', async () => {
|
test('should capture with transparent background and copy to clipboard', async () => {
|
||||||
await screenshotSelector.init('transparent', true);
|
await screenshotSelector.init('transparent', true);
|
||||||
|
|
||||||
const result = await screenshotSelector.captureElement(mockElement);
|
const result = await screenshotSelector.captureElement(mockElement);
|
||||||
|
|
||||||
expect(global.htmlToImage.toCanvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({
|
expect(global.html2canvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({
|
||||||
backgroundColor: undefined, // Transparent background
|
backgroundColor: null, // Transparent background
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
|
@ -281,11 +275,13 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
|
|
||||||
test('should capture with black background and copy to clipboard', async () => {
|
test('should capture with black background and copy to clipboard', async () => {
|
||||||
await screenshotSelector.init('black', true);
|
await screenshotSelector.init('black', true);
|
||||||
|
|
||||||
const result = await screenshotSelector.captureElement(mockElement);
|
const result = await screenshotSelector.captureElement(mockElement);
|
||||||
|
|
||||||
expect(global.htmlToImage.toCanvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({
|
expect(global.html2canvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({
|
||||||
backgroundColor: '#000000', // Black background
|
backgroundColor: '#000000', // Black background
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
|
@ -295,11 +291,13 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
|
|
||||||
test('should capture with white background and copy to clipboard', async () => {
|
test('should capture with white background and copy to clipboard', async () => {
|
||||||
await screenshotSelector.init('white', true);
|
await screenshotSelector.init('white', true);
|
||||||
|
|
||||||
const result = await screenshotSelector.captureElement(mockElement);
|
const result = await screenshotSelector.captureElement(mockElement);
|
||||||
|
|
||||||
expect(global.htmlToImage.toCanvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({
|
expect(global.html2canvas).toHaveBeenCalledWith(mockElement, expect.objectContaining({
|
||||||
backgroundColor: '#ffffff', // White background
|
backgroundColor: '#ffffff', // White background
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
|
@ -309,7 +307,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
|
|
||||||
test('should capture without clipboard when disabled', async () => {
|
test('should capture without clipboard when disabled', async () => {
|
||||||
await screenshotSelector.init('black', false, true); // saveToPc = true, copyToClipboard = false
|
await screenshotSelector.init('black', false, true); // saveToPc = true, copyToClipboard = false
|
||||||
|
|
||||||
const result = await screenshotSelector.captureElement(mockElement);
|
const result = await screenshotSelector.captureElement(mockElement);
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
|
@ -326,9 +324,9 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
global.navigator.clipboard = {
|
global.navigator.clipboard = {
|
||||||
write: jest.fn().mockRejectedValue(new DOMException('Permission denied', 'NotAllowedError'))
|
write: jest.fn().mockRejectedValue(new DOMException('Permission denied', 'NotAllowedError'))
|
||||||
};
|
};
|
||||||
|
|
||||||
await screenshotSelector.init('black', true);
|
await screenshotSelector.init('black', true);
|
||||||
|
|
||||||
const result = await screenshotSelector.captureElement(mockElement);
|
const result = await screenshotSelector.captureElement(mockElement);
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
|
@ -342,9 +340,9 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
||||||
test('should handle clipboard API unavailable scenario', async () => {
|
test('should handle clipboard API unavailable scenario', async () => {
|
||||||
// Mock clipboard API not available
|
// Mock clipboard API not available
|
||||||
global.navigator.clipboard = undefined;
|
global.navigator.clipboard = undefined;
|
||||||
|
|
||||||
await screenshotSelector.init('transparent', true);
|
await screenshotSelector.init('transparent', true);
|
||||||
|
|
||||||
const result = await screenshotSelector.captureElement(mockElement);
|
const result = await screenshotSelector.captureElement(mockElement);
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
|
@ -361,7 +359,7 @@ describe('Integration Scenarios - Complete Workflow', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Mock popup elements
|
// Mock popup elements
|
||||||
mockPopupElements = {
|
mockPopupElements = {
|
||||||
clipboardToggle: { checked: true, addEventListener: jest.fn() },
|
clipboardToggle: { checked: true, addEventListener: jest.fn() },
|
||||||
|
|
@ -401,18 +399,18 @@ describe('Integration Scenarios - Complete Workflow', () => {
|
||||||
const backgroundSelect = document.getElementById('background-select');
|
const backgroundSelect = document.getElementById('background-select');
|
||||||
const clipboardToggle = document.getElementById('clipboard-toggle');
|
const clipboardToggle = document.getElementById('clipboard-toggle');
|
||||||
const startBtn = document.getElementById('start-selector');
|
const startBtn = document.getElementById('start-selector');
|
||||||
|
|
||||||
// Load preferences
|
// Load preferences
|
||||||
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard']);
|
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard']);
|
||||||
if (saved.backgroundPreference) {
|
if (saved.backgroundPreference) {
|
||||||
backgroundSelect.value = saved.backgroundPreference;
|
backgroundSelect.value = saved.backgroundPreference;
|
||||||
}
|
}
|
||||||
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
||||||
|
|
||||||
// Start selector
|
// Start selector
|
||||||
startBtn.addEventListener('click', async () => {
|
startBtn.addEventListener('click', async () => {
|
||||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
|
||||||
await chrome.tabs.sendMessage(tab.id, {
|
await chrome.tabs.sendMessage(tab.id, {
|
||||||
action: 'startSelector',
|
action: 'startSelector',
|
||||||
background: backgroundSelect.value,
|
background: backgroundSelect.value,
|
||||||
|
|
@ -468,12 +466,12 @@ describe('Integration Scenarios - Complete Workflow', () => {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const popupScript = `
|
const popupScript = `
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const backgroundSelect = document.getElementById('background-select');
|
const backgroundSelect = document.getElementById('background-select');
|
||||||
const clipboardToggle = document.getElementById('clipboard-toggle');
|
const clipboardToggle = document.getElementById('clipboard-toggle');
|
||||||
|
|
||||||
backgroundSelect.addEventListener('change', () => {
|
backgroundSelect.addEventListener('change', () => {
|
||||||
chrome.storage.sync.set({ backgroundPreference: backgroundSelect.value });
|
chrome.storage.sync.set({ backgroundPreference: backgroundSelect.value });
|
||||||
});
|
});
|
||||||
|
|
@ -501,9 +499,9 @@ describe('Integration Scenarios - Complete Workflow', () => {
|
||||||
|
|
||||||
test('should handle incompatible pages gracefully', async () => {
|
test('should handle incompatible pages gracefully', async () => {
|
||||||
// Mock incompatible page
|
// Mock incompatible page
|
||||||
global.chrome.tabs.query.mockResolvedValue([{
|
global.chrome.tabs.query.mockResolvedValue([{
|
||||||
id: 123,
|
id: 123,
|
||||||
url: 'chrome://settings/'
|
url: 'chrome://settings/'
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
let startButtonCallback;
|
let startButtonCallback;
|
||||||
|
|
@ -516,7 +514,7 @@ describe('Integration Scenarios - Complete Workflow', () => {
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const startBtn = document.getElementById('start-selector');
|
const startBtn = document.getElementById('start-selector');
|
||||||
const status = document.getElementById('status');
|
const status = document.getElementById('status');
|
||||||
|
|
||||||
startBtn.addEventListener('click', async () => {
|
startBtn.addEventListener('click', async () => {
|
||||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
|
||||||
|
|
@ -529,7 +527,7 @@ describe('Integration Scenarios - Complete Workflow', () => {
|
||||||
status.style.display = 'block';
|
status.style.display = 'block';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal flow would continue here
|
// Normal flow would continue here
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,8 @@ global.window = {
|
||||||
CSS: { escape: jest.fn(str => str) }
|
CSS: { escape: jest.fn(str => str) }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock html-to-image
|
// Mock html2canvas
|
||||||
global.htmlToImage = {
|
global.html2canvas = jest.fn();
|
||||||
toCanvas: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock ScreenshotSelector class for testing
|
// Mock ScreenshotSelector class for testing
|
||||||
class MockScreenshotSelector {
|
class MockScreenshotSelector {
|
||||||
|
|
@ -128,18 +126,10 @@ class MockScreenshotSelector {
|
||||||
toDataURL: jest.fn(() => 'data:image/png;base64,mockdata')
|
toDataURL: jest.fn(() => 'data:image/png;base64,mockdata')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock html-to-image
|
// Mock html2canvas
|
||||||
global.htmlToImage.toCanvas.mockResolvedValue(mockCanvas);
|
global.html2canvas.mockResolvedValue(mockCanvas);
|
||||||
|
|
||||||
const canvas = await global.htmlToImage.toCanvas(element, {
|
const canvas = await global.html2canvas(element);
|
||||||
backgroundColor: this.background === 'transparent'
|
|
||||||
? undefined
|
|
||||||
: this.background === 'white'
|
|
||||||
? '#ffffff'
|
|
||||||
: '#000000',
|
|
||||||
pixelRatio: global.window.devicePixelRatio || 1,
|
|
||||||
cacheBust: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Conditionally download based on saveToPc setting
|
// Conditionally download based on saveToPc setting
|
||||||
if (this.saveToPc) {
|
if (this.saveToPc) {
|
||||||
|
|
@ -169,7 +159,7 @@ describe('Save to PC and Clipboard Combinations', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Reset mocks
|
// Reset mocks
|
||||||
mockCreateElement.mockClear();
|
mockCreateElement.mockClear();
|
||||||
mockCreateElement.mockReturnValue(mockLink);
|
mockCreateElement.mockReturnValue(mockLink);
|
||||||
|
|
@ -178,7 +168,7 @@ describe('Save to PC and Clipboard Combinations', () => {
|
||||||
global.document.createElement = mockCreateElement;
|
global.document.createElement = mockCreateElement;
|
||||||
}
|
}
|
||||||
mockLink.click.mockClear();
|
mockLink.click.mockClear();
|
||||||
|
|
||||||
// Setup clipboard API mocks
|
// Setup clipboard API mocks
|
||||||
global.navigator = {
|
global.navigator = {
|
||||||
clipboard: {
|
clipboard: {
|
||||||
|
|
@ -217,7 +207,7 @@ describe('Save to PC and Clipboard Combinations', () => {
|
||||||
// Verify download was triggered
|
// Verify download was triggered
|
||||||
expect(mockCreateElement).toHaveBeenCalledWith('a');
|
expect(mockCreateElement).toHaveBeenCalledWith('a');
|
||||||
expect(mockLink.click).toHaveBeenCalled();
|
expect(mockLink.click).toHaveBeenCalled();
|
||||||
|
|
||||||
// Verify clipboard operation was attempted
|
// Verify clipboard operation was attempted
|
||||||
expect(global.navigator.clipboard.write).toHaveBeenCalled();
|
expect(global.navigator.clipboard.write).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
@ -266,7 +256,7 @@ describe('Save to PC and Clipboard Combinations', () => {
|
||||||
// Verify download was triggered
|
// Verify download was triggered
|
||||||
expect(mockCreateElement).toHaveBeenCalledWith('a');
|
expect(mockCreateElement).toHaveBeenCalledWith('a');
|
||||||
expect(mockLink.click).toHaveBeenCalled();
|
expect(mockLink.click).toHaveBeenCalled();
|
||||||
|
|
||||||
// Verify clipboard operation was NOT attempted
|
// Verify clipboard operation was NOT attempted
|
||||||
expect(global.navigator.clipboard.write).not.toHaveBeenCalled();
|
expect(global.navigator.clipboard.write).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
@ -297,7 +287,7 @@ describe('Save to PC and Clipboard Combinations', () => {
|
||||||
|
|
||||||
// Verify download was NOT triggered
|
// Verify download was NOT triggered
|
||||||
expect(mockCreateElement).not.toHaveBeenCalledWith('a');
|
expect(mockCreateElement).not.toHaveBeenCalledWith('a');
|
||||||
|
|
||||||
// Verify clipboard operation was attempted
|
// Verify clipboard operation was attempted
|
||||||
expect(global.navigator.clipboard.write).toHaveBeenCalled();
|
expect(global.navigator.clipboard.write).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
@ -327,7 +317,7 @@ describe('Save to PC and Clipboard Combinations', () => {
|
||||||
|
|
||||||
// Verify download was NOT triggered
|
// Verify download was NOT triggered
|
||||||
expect(mockCreateElement).not.toHaveBeenCalledWith('a');
|
expect(mockCreateElement).not.toHaveBeenCalledWith('a');
|
||||||
|
|
||||||
// Verify clipboard operation was NOT attempted
|
// Verify clipboard operation was NOT attempted
|
||||||
expect(global.navigator.clipboard.write).not.toHaveBeenCalled();
|
expect(global.navigator.clipboard.write).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
@ -344,11 +334,8 @@ describe('Save to PC and Clipboard Combinations', () => {
|
||||||
test('should still create canvas even when both outputs are disabled', async () => {
|
test('should still create canvas even when both outputs are disabled', async () => {
|
||||||
await screenshotSelector.captureElement(mockElement);
|
await screenshotSelector.captureElement(mockElement);
|
||||||
|
|
||||||
// html-to-image should still be called (needed for potential future operations)
|
// html2canvas should still be called (needed for potential future operations)
|
||||||
expect(global.htmlToImage.toCanvas).toHaveBeenCalledWith(
|
expect(global.html2canvas).toHaveBeenCalledWith(mockElement);
|
||||||
mockElement,
|
|
||||||
expect.any(Object)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -362,24 +349,24 @@ describe('Popup Validation Logic for All Combinations', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Mock DOM elements
|
// Mock DOM elements
|
||||||
clipboardToggle = {
|
clipboardToggle = {
|
||||||
checked: true,
|
checked: true,
|
||||||
addEventListener: jest.fn()
|
addEventListener: jest.fn()
|
||||||
};
|
};
|
||||||
|
|
||||||
saveToPcToggle = {
|
saveToPcToggle = {
|
||||||
checked: true,
|
checked: true,
|
||||||
addEventListener: jest.fn()
|
addEventListener: jest.fn()
|
||||||
};
|
};
|
||||||
|
|
||||||
startBtn = {
|
startBtn = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
setAttribute: jest.fn(),
|
setAttribute: jest.fn(),
|
||||||
addEventListener: jest.fn()
|
addEventListener: jest.fn()
|
||||||
};
|
};
|
||||||
|
|
||||||
validationWarning = {
|
validationWarning = {
|
||||||
style: { display: 'none' }
|
style: { display: 'none' }
|
||||||
};
|
};
|
||||||
|
|
@ -397,7 +384,7 @@ describe('Popup Validation Logic for All Combinations', () => {
|
||||||
// Define validation function
|
// Define validation function
|
||||||
validateOutputMethods = function() {
|
validateOutputMethods = function() {
|
||||||
const hasValidOutput = clipboardToggle.checked || saveToPcToggle.checked;
|
const hasValidOutput = clipboardToggle.checked || saveToPcToggle.checked;
|
||||||
|
|
||||||
if (hasValidOutput) {
|
if (hasValidOutput) {
|
||||||
validationWarning.style.display = 'none';
|
validationWarning.style.display = 'none';
|
||||||
startBtn.disabled = false;
|
startBtn.disabled = false;
|
||||||
|
|
@ -407,7 +394,7 @@ describe('Popup Validation Logic for All Combinations', () => {
|
||||||
startBtn.disabled = true;
|
startBtn.disabled = true;
|
||||||
startBtn.setAttribute('aria-describedby', 'validation-warning');
|
startBtn.setAttribute('aria-describedby', 'validation-warning');
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasValidOutput;
|
return hasValidOutput;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
@ -467,13 +454,13 @@ describe('Preference Persistence Across Browser Sessions', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Mock DOM elements
|
// Mock DOM elements
|
||||||
mockClipboardToggle = {
|
mockClipboardToggle = {
|
||||||
checked: false,
|
checked: false,
|
||||||
addEventListener: jest.fn()
|
addEventListener: jest.fn()
|
||||||
};
|
};
|
||||||
|
|
||||||
mockSaveToPcToggle = {
|
mockSaveToPcToggle = {
|
||||||
checked: false,
|
checked: false,
|
||||||
addEventListener: jest.fn()
|
addEventListener: jest.fn()
|
||||||
|
|
@ -494,13 +481,13 @@ describe('Preference Persistence Across Browser Sessions', () => {
|
||||||
test('should default to both enabled for new installations', async () => {
|
test('should default to both enabled for new installations', async () => {
|
||||||
// Mock no saved preferences (new installation)
|
// Mock no saved preferences (new installation)
|
||||||
global.chrome.storage.sync.get.mockResolvedValueOnce({});
|
global.chrome.storage.sync.get.mockResolvedValueOnce({});
|
||||||
|
|
||||||
const popupScript = `
|
const popupScript = `
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const clipboardToggle = document.getElementById('clipboard-toggle');
|
const clipboardToggle = document.getElementById('clipboard-toggle');
|
||||||
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
||||||
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
||||||
|
|
||||||
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
||||||
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
||||||
});
|
});
|
||||||
|
|
@ -526,13 +513,13 @@ describe('Preference Persistence Across Browser Sessions', () => {
|
||||||
copyToClipboard: false,
|
copyToClipboard: false,
|
||||||
saveToPc: true
|
saveToPc: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const popupScript = `
|
const popupScript = `
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const clipboardToggle = document.getElementById('clipboard-toggle');
|
const clipboardToggle = document.getElementById('clipboard-toggle');
|
||||||
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
||||||
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
||||||
|
|
||||||
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
||||||
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
||||||
});
|
});
|
||||||
|
|
@ -558,13 +545,13 @@ describe('Preference Persistence Across Browser Sessions', () => {
|
||||||
copyToClipboard: true,
|
copyToClipboard: true,
|
||||||
saveToPc: false
|
saveToPc: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const popupScript = `
|
const popupScript = `
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const clipboardToggle = document.getElementById('clipboard-toggle');
|
const clipboardToggle = document.getElementById('clipboard-toggle');
|
||||||
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
||||||
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
||||||
|
|
||||||
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
||||||
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
||||||
});
|
});
|
||||||
|
|
@ -596,7 +583,7 @@ describe('Preference Persistence Across Browser Sessions', () => {
|
||||||
const clipboardToggle = document.getElementById('clipboard-toggle');
|
const clipboardToggle = document.getElementById('clipboard-toggle');
|
||||||
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
||||||
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
||||||
|
|
||||||
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
||||||
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
||||||
});
|
});
|
||||||
|
|
@ -614,7 +601,7 @@ describe('Preference Persistence Across Browser Sessions', () => {
|
||||||
|
|
||||||
expect(mockClipboardToggle.checked).toBe(false);
|
expect(mockClipboardToggle.checked).toBe(false);
|
||||||
expect(mockSaveToPcToggle.checked).toBe(false);
|
expect(mockSaveToPcToggle.checked).toBe(false);
|
||||||
|
|
||||||
// Validation should fail with both disabled
|
// Validation should fail with both disabled
|
||||||
const isValid = mockClipboardToggle.checked || mockSaveToPcToggle.checked;
|
const isValid = mockClipboardToggle.checked || mockSaveToPcToggle.checked;
|
||||||
expect(isValid).toBe(false);
|
expect(isValid).toBe(false);
|
||||||
|
|
@ -626,13 +613,13 @@ describe('Preference Persistence Across Browser Sessions', () => {
|
||||||
copyToClipboard: true
|
copyToClipboard: true
|
||||||
// saveToPc is undefined (not set in previous version)
|
// saveToPc is undefined (not set in previous version)
|
||||||
});
|
});
|
||||||
|
|
||||||
const popupScript = `
|
const popupScript = `
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const clipboardToggle = document.getElementById('clipboard-toggle');
|
const clipboardToggle = document.getElementById('clipboard-toggle');
|
||||||
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
|
||||||
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
|
||||||
|
|
||||||
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
|
||||||
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -233,15 +233,13 @@ global.mockClipboardAPI = (scenario = 'supported') => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Clear all mocks first
|
// Clear all mocks first
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
global.mockChromeAPIs();
|
global.mockChromeAPIs();
|
||||||
global.mockDOMAPIs();
|
global.mockDOMAPIs();
|
||||||
global.mockClipboardAPI('supported');
|
global.mockClipboardAPI('supported');
|
||||||
|
|
||||||
// Mock html-to-image
|
// Mock html2canvas
|
||||||
global.htmlToImage = {
|
global.html2canvas = jest.fn().mockResolvedValue(global.createMockCanvas());
|
||||||
toCanvas: jest.fn().mockResolvedValue(global.createMockCanvas()),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cleanup after each test
|
// Cleanup after each test
|
||||||
|
|
@ -253,13 +251,13 @@ afterEach(() => {
|
||||||
expect.extend({
|
expect.extend({
|
||||||
toHaveBeenCalledWithClipboardMessage(received, expectedBackground, expectedClipboard) {
|
toHaveBeenCalledWithClipboardMessage(received, expectedBackground, expectedClipboard) {
|
||||||
const calls = received.mock.calls;
|
const calls = received.mock.calls;
|
||||||
const matchingCall = calls.find(call =>
|
const matchingCall = calls.find(call =>
|
||||||
call[1] &&
|
call[1] &&
|
||||||
call[1].action === 'startSelector' &&
|
call[1].action === 'startSelector' &&
|
||||||
call[1].background === expectedBackground &&
|
call[1].background === expectedBackground &&
|
||||||
call[1].copyToClipboard === expectedClipboard
|
call[1].copyToClipboard === expectedClipboard
|
||||||
);
|
);
|
||||||
|
|
||||||
if (matchingCall) {
|
if (matchingCall) {
|
||||||
return {
|
return {
|
||||||
message: () => `Expected not to be called with clipboard message`,
|
message: () => `Expected not to be called with clipboard message`,
|
||||||
|
|
@ -272,7 +270,7 @@ expect.extend({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toHaveClipboardResult(received, expectedSuccess, expectedErrorType) {
|
toHaveClipboardResult(received, expectedSuccess, expectedErrorType) {
|
||||||
if (!received.clipboardResult) {
|
if (!received.clipboardResult) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -280,10 +278,10 @@ expect.extend({
|
||||||
pass: false
|
pass: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = received.clipboardResult.success === expectedSuccess;
|
const success = received.clipboardResult.success === expectedSuccess;
|
||||||
const errorType = !expectedErrorType || received.clipboardResult.errorType === expectedErrorType;
|
const errorType = !expectedErrorType || received.clipboardResult.errorType === expectedErrorType;
|
||||||
|
|
||||||
if (success && errorType) {
|
if (success && errorType) {
|
||||||
return {
|
return {
|
||||||
message: () => `Expected clipboard result not to match`,
|
message: () => `Expected clipboard result not to match`,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue