screenshot-chrome-extension/tests/save-to-pc-clipboard-combinations.test.js
Karthikeyan N a018c36d67
Add copy to clipboard and save to pc toggle.
* escaped node modules from vcs

* feat: added copy to clipboard feature

* feat: added tests

* feat: added feature spec

* feat: added toggle for save to pc

* feat: added tests

* refactor: change toggle from '?' button to the 'switch' component

* Fix failing tests

* Update gitignore and remove unnecessary files

* Remove tooltips because they are unnecessary

---------

Co-authored-by: Joey Yakimowich-Payne <jyapayne@gmail.com>
2025-08-13 13:29:57 -06:00

641 lines
No EOL
21 KiB
JavaScript

/**
* Comprehensive test suite for all combinations of save to PC and clipboard settings
* Tests all four combinations: both enabled, save-only, clipboard-only, both disabled
* Includes preference persistence testing across browser sessions
*/
// Mock Chrome APIs
global.chrome = {
storage: {
sync: {
get: jest.fn(),
set: jest.fn()
}
},
runtime: {
sendMessage: jest.fn(),
onMessage: {
addListener: jest.fn()
}
},
tabs: {
query: jest.fn(),
sendMessage: jest.fn()
}
};
// Mock DOM APIs
const mockCreateElement = jest.fn();
const mockLink = {
href: '',
download: '',
click: jest.fn(),
style: {},
classList: { add: jest.fn(), remove: jest.fn() }
};
global.document = {
createElement: mockCreateElement,
head: { appendChild: jest.fn() },
body: { appendChild: jest.fn() },
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
querySelectorAll: jest.fn(() => []),
getElementById: jest.fn()
};
global.window = {
close: jest.fn(),
setTimeout: jest.fn((fn) => fn()),
getComputedStyle: jest.fn(() => ({})),
devicePixelRatio: 2,
isSecureContext: true,
CSS: { escape: jest.fn(str => str) }
};
// Mock html2canvas
global.html2canvas = jest.fn();
// Mock ScreenshotSelector class for testing
class MockScreenshotSelector {
constructor() {
this.copyToClipboard = true;
this.saveToPc = true;
this.background = 'black';
this.isActive = false;
}
async init(background = 'black', copyToClipboard = true, saveToPc = true) {
this.background = background;
this.copyToClipboard = copyToClipboard;
this.saveToPc = saveToPc;
this.isActive = true;
}
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 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' };
} else if (error.name === 'SecurityError') {
return { success: false, error: 'Security error', errorType: 'security_error' };
}
return { success: false, error: error.message, errorType: 'unexpected' };
}
}
async captureElement(element) {
// Mock canvas creation
const mockCanvas = {
toBlob: jest.fn((callback) => {
const mockBlob = new Blob(['mock data'], { type: 'image/png' });
callback(mockBlob);
}),
toDataURL: jest.fn(() => 'data:image/png;base64,mockdata')
};
// Mock html2canvas
global.html2canvas.mockResolvedValue(mockCanvas);
const canvas = await global.html2canvas(element);
// Conditionally download based on saveToPc setting
if (this.saveToPc) {
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 operations if enabled
let clipboardResult = null;
if (this.copyToClipboard) {
clipboardResult = await this.copyCanvasToClipboard(canvas);
}
return {
success: true,
downloadPerformed: this.saveToPc,
clipboardResult: clipboardResult
};
}
}
describe('Save to PC and Clipboard Combinations', () => {
let screenshotSelector;
let mockElement;
beforeEach(() => {
jest.clearAllMocks();
// Reset mocks
mockCreateElement.mockClear();
mockCreateElement.mockReturnValue(mockLink);
// Ensure our mock is used even if the test environment adjusted document
if (global.document) {
global.document.createElement = mockCreateElement;
}
mockLink.click.mockClear();
// Setup clipboard API mocks
global.navigator = {
clipboard: {
write: jest.fn().mockResolvedValue()
}
};
global.window.ClipboardItem = jest.fn();
global.window.isSecureContext = true;
screenshotSelector = new MockScreenshotSelector();
mockElement = {
classList: { remove: jest.fn() },
style: {},
getBoundingClientRect: () => ({ left: 0, top: 0, width: 100, height: 100 }),
scrollHeight: 100,
clientHeight: 100,
scrollWidth: 100,
clientWidth: 100
};
});
describe('Combination 1: Both Save to PC and Clipboard Enabled (saveToPc: true, clipboard: true)', () => {
beforeEach(() => {
screenshotSelector.saveToPc = true;
screenshotSelector.copyToClipboard = true;
});
test('should perform both download and clipboard operations when both are enabled', async () => {
const result = await screenshotSelector.captureElement(mockElement);
expect(result.success).toBe(true);
expect(result.downloadPerformed).toBe(true);
expect(result.clipboardResult).toBeDefined();
expect(result.clipboardResult.success).toBe(true);
// Verify download was triggered
expect(mockCreateElement).toHaveBeenCalledWith('a');
expect(mockLink.click).toHaveBeenCalled();
// Verify clipboard operation was attempted
expect(global.navigator.clipboard.write).toHaveBeenCalled();
});
test('should succeed with download even if clipboard fails when both are enabled', async () => {
// Mock clipboard failure
if (global.navigator.clipboard && global.navigator.clipboard.write) {
global.navigator.clipboard.write.mockRejectedValue(new DOMException('Permission denied', 'NotAllowedError'));
}
const result = await screenshotSelector.captureElement(mockElement);
expect(result.success).toBe(true);
expect(result.downloadPerformed).toBe(true);
expect(result.clipboardResult.success).toBe(false);
expect(result.clipboardResult.errorType).toBe('permission_denied');
// Download should still work
expect(mockCreateElement).toHaveBeenCalledWith('a');
expect(mockLink.click).toHaveBeenCalled();
});
test('should initialize with both settings correctly', async () => {
await screenshotSelector.init('white', true, true);
expect(screenshotSelector.background).toBe('white');
expect(screenshotSelector.copyToClipboard).toBe(true);
expect(screenshotSelector.saveToPc).toBe(true);
expect(screenshotSelector.isActive).toBe(true);
});
});
describe('Combination 2: Save-only Mode (saveToPc: true, clipboard: false)', () => {
beforeEach(() => {
screenshotSelector.saveToPc = true;
screenshotSelector.copyToClipboard = false;
});
test('should only perform download when save-only mode is enabled', async () => {
const result = await screenshotSelector.captureElement(mockElement);
expect(result.success).toBe(true);
expect(result.downloadPerformed).toBe(true);
expect(result.clipboardResult).toBeNull();
// Verify download was triggered
expect(mockCreateElement).toHaveBeenCalledWith('a');
expect(mockLink.click).toHaveBeenCalled();
// Verify clipboard operation was NOT attempted
expect(global.navigator.clipboard.write).not.toHaveBeenCalled();
});
test('should initialize with save-only settings correctly', async () => {
await screenshotSelector.init('transparent', false, true);
expect(screenshotSelector.background).toBe('transparent');
expect(screenshotSelector.copyToClipboard).toBe(false);
expect(screenshotSelector.saveToPc).toBe(true);
expect(screenshotSelector.isActive).toBe(true);
});
});
describe('Combination 3: Clipboard-only Mode (saveToPc: false, clipboard: true)', () => {
beforeEach(() => {
screenshotSelector.saveToPc = false;
screenshotSelector.copyToClipboard = true;
});
test('should only perform clipboard operation when clipboard-only mode is enabled', async () => {
const result = await screenshotSelector.captureElement(mockElement);
expect(result.success).toBe(true);
expect(result.downloadPerformed).toBe(false);
expect(result.clipboardResult).toBeDefined();
expect(result.clipboardResult.success).toBe(true);
// Verify download was NOT triggered
expect(mockCreateElement).not.toHaveBeenCalledWith('a');
// Verify clipboard operation was attempted
expect(global.navigator.clipboard.write).toHaveBeenCalled();
});
test('should initialize with clipboard-only settings correctly', async () => {
await screenshotSelector.init('black', true, false);
expect(screenshotSelector.background).toBe('black');
expect(screenshotSelector.copyToClipboard).toBe(true);
expect(screenshotSelector.saveToPc).toBe(false);
expect(screenshotSelector.isActive).toBe(true);
});
});
describe('Combination 4: Both Disabled (saveToPc: false, clipboard: false)', () => {
beforeEach(() => {
screenshotSelector.saveToPc = false;
screenshotSelector.copyToClipboard = false;
});
test('should perform neither operation when both are disabled', async () => {
const result = await screenshotSelector.captureElement(mockElement);
expect(result.success).toBe(true);
expect(result.downloadPerformed).toBe(false);
expect(result.clipboardResult).toBeNull();
// Verify download was NOT triggered
expect(mockCreateElement).not.toHaveBeenCalledWith('a');
// Verify clipboard operation was NOT attempted
expect(global.navigator.clipboard.write).not.toHaveBeenCalled();
});
test('should initialize with both disabled settings correctly', async () => {
await screenshotSelector.init('white', false, false);
expect(screenshotSelector.background).toBe('white');
expect(screenshotSelector.copyToClipboard).toBe(false);
expect(screenshotSelector.saveToPc).toBe(false);
expect(screenshotSelector.isActive).toBe(true);
});
test('should still create canvas even when both outputs are disabled', async () => {
await screenshotSelector.captureElement(mockElement);
// html2canvas should still be called (needed for potential future operations)
expect(global.html2canvas).toHaveBeenCalledWith(mockElement);
});
});
});
describe('Popup Validation Logic for All Combinations', () => {
let clipboardToggle;
let saveToPcToggle;
let startBtn;
let validationWarning;
let validateOutputMethods;
beforeEach(() => {
jest.clearAllMocks();
// Mock DOM elements
clipboardToggle = {
checked: true,
addEventListener: jest.fn()
};
saveToPcToggle = {
checked: true,
addEventListener: jest.fn()
};
startBtn = {
disabled: false,
setAttribute: jest.fn(),
addEventListener: jest.fn()
};
validationWarning = {
style: { display: 'none' }
};
global.document.getElementById = jest.fn((id) => {
switch (id) {
case 'clipboard-toggle': return clipboardToggle;
case 'save-to-pc-toggle': return saveToPcToggle;
case 'start-selector': return startBtn;
case 'validation-warning': return validationWarning;
default: return null;
}
});
// Define validation function
validateOutputMethods = function() {
const hasValidOutput = clipboardToggle.checked || saveToPcToggle.checked;
if (hasValidOutput) {
validationWarning.style.display = 'none';
startBtn.disabled = false;
startBtn.setAttribute('aria-describedby', '');
} else {
validationWarning.style.display = 'block';
startBtn.disabled = true;
startBtn.setAttribute('aria-describedby', 'validation-warning');
}
return hasValidOutput;
};
});
test('should validate successfully when both save to PC and clipboard are enabled', () => {
clipboardToggle.checked = true;
saveToPcToggle.checked = true;
const result = validateOutputMethods();
expect(result).toBe(true);
expect(validationWarning.style.display).toBe('none');
expect(startBtn.disabled).toBe(false);
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', '');
});
test('should validate successfully when only save to PC is enabled', () => {
clipboardToggle.checked = false;
saveToPcToggle.checked = true;
const result = validateOutputMethods();
expect(result).toBe(true);
expect(validationWarning.style.display).toBe('none');
expect(startBtn.disabled).toBe(false);
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', '');
});
test('should validate successfully when only clipboard is enabled', () => {
clipboardToggle.checked = true;
saveToPcToggle.checked = false;
const result = validateOutputMethods();
expect(result).toBe(true);
expect(validationWarning.style.display).toBe('none');
expect(startBtn.disabled).toBe(false);
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', '');
});
test('should fail validation when both save to PC and clipboard are disabled', () => {
clipboardToggle.checked = false;
saveToPcToggle.checked = false;
const result = validateOutputMethods();
expect(result).toBe(false);
expect(validationWarning.style.display).toBe('block');
expect(startBtn.disabled).toBe(true);
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', 'validation-warning');
});
});
describe('Preference Persistence Across Browser Sessions', () => {
let mockClipboardToggle;
let mockSaveToPcToggle;
beforeEach(() => {
jest.clearAllMocks();
// Mock DOM elements
mockClipboardToggle = {
checked: false,
addEventListener: jest.fn()
};
mockSaveToPcToggle = {
checked: false,
addEventListener: jest.fn()
};
global.document.getElementById = jest.fn((id) => {
switch (id) {
case 'clipboard-toggle':
return mockClipboardToggle;
case 'save-to-pc-toggle':
return mockSaveToPcToggle;
default:
return null;
}
});
});
test('should default to both enabled for new installations', async () => {
// Mock no saved preferences (new installation)
global.chrome.storage.sync.get.mockResolvedValueOnce({});
const popupScript = `
document.addEventListener('DOMContentLoaded', async () => {
const clipboardToggle = document.getElementById('clipboard-toggle');
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
});
`;
await new Promise(resolve => {
global.document.addEventListener = jest.fn((event, callback) => {
if (event === 'DOMContentLoaded') {
callback();
resolve();
}
});
eval(popupScript);
});
expect(mockClipboardToggle.checked).toBe(true); // Default
expect(mockSaveToPcToggle.checked).toBe(true); // Default
});
test('should persist save-only preferences across sessions', async () => {
// First session - user sets save-only mode
global.chrome.storage.sync.get.mockResolvedValueOnce({
copyToClipboard: false,
saveToPc: true
});
const popupScript = `
document.addEventListener('DOMContentLoaded', async () => {
const clipboardToggle = document.getElementById('clipboard-toggle');
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
});
`;
await new Promise(resolve => {
global.document.addEventListener = jest.fn((event, callback) => {
if (event === 'DOMContentLoaded') {
callback();
resolve();
}
});
eval(popupScript);
});
expect(mockClipboardToggle.checked).toBe(false);
expect(mockSaveToPcToggle.checked).toBe(true);
});
test('should persist clipboard-only preferences across sessions', async () => {
// Load clipboard-only mode
global.chrome.storage.sync.get.mockResolvedValueOnce({
copyToClipboard: true,
saveToPc: false
});
const popupScript = `
document.addEventListener('DOMContentLoaded', async () => {
const clipboardToggle = document.getElementById('clipboard-toggle');
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
});
`;
await new Promise(resolve => {
global.document.addEventListener = jest.fn((event, callback) => {
if (event === 'DOMContentLoaded') {
callback();
resolve();
}
});
eval(popupScript);
});
expect(mockClipboardToggle.checked).toBe(true);
expect(mockSaveToPcToggle.checked).toBe(false);
});
test('should persist both disabled preferences across sessions', async () => {
// Load both disabled state
global.chrome.storage.sync.get.mockResolvedValueOnce({
copyToClipboard: false,
saveToPc: false
});
const popupScript = `
document.addEventListener('DOMContentLoaded', async () => {
const clipboardToggle = document.getElementById('clipboard-toggle');
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
});
`;
await new Promise(resolve => {
global.document.addEventListener = jest.fn((event, callback) => {
if (event === 'DOMContentLoaded') {
callback();
resolve();
}
});
eval(popupScript);
});
expect(mockClipboardToggle.checked).toBe(false);
expect(mockSaveToPcToggle.checked).toBe(false);
// Validation should fail with both disabled
const isValid = mockClipboardToggle.checked || mockSaveToPcToggle.checked;
expect(isValid).toBe(false);
});
test('should maintain existing behavior when upgrading from version without saveToPc', async () => {
// Mock existing installation with only clipboard preference
global.chrome.storage.sync.get.mockResolvedValueOnce({
copyToClipboard: true
// saveToPc is undefined (not set in previous version)
});
const popupScript = `
document.addEventListener('DOMContentLoaded', async () => {
const clipboardToggle = document.getElementById('clipboard-toggle');
const saveToPcToggle = document.getElementById('save-to-pc-toggle');
const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']);
clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true;
saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true;
});
`;
await new Promise(resolve => {
global.document.addEventListener = jest.fn((event, callback) => {
if (event === 'DOMContentLoaded') {
callback();
resolve();
}
});
eval(popupScript);
});
expect(mockClipboardToggle.checked).toBe(true); // Existing preference
expect(mockSaveToPcToggle.checked).toBe(true); // Default for backward compatibility
});
});