feat: added tests
This commit is contained in:
parent
5af01454ab
commit
fdac507802
3 changed files with 908 additions and 26 deletions
|
|
@ -64,6 +64,18 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
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: {
|
||||
|
|
@ -92,13 +104,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
|
||||
global.html2canvas = jest.fn().mockResolvedValue(mockCanvas);
|
||||
|
||||
// Mock full clipboard API support
|
||||
global.navigator = {
|
||||
clipboard: {
|
||||
write: jest.fn().mockResolvedValue()
|
||||
}
|
||||
};
|
||||
global.window.ClipboardItem = jest.fn();
|
||||
|
||||
|
||||
// Import ScreenshotSelector (simplified for testing)
|
||||
const ScreenshotSelector = class {
|
||||
|
|
@ -106,12 +112,14 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
this.isActive = false;
|
||||
this.background = 'black';
|
||||
this.copyToClipboard = true;
|
||||
this.saveToPc = true;
|
||||
this.overlay = null;
|
||||
}
|
||||
|
||||
async init(background = 'black', copyToClipboard = true) {
|
||||
async init(background = 'black', copyToClipboard = true, saveToPc = true) {
|
||||
this.background = background;
|
||||
this.copyToClipboard = copyToClipboard;
|
||||
this.saveToPc = saveToPc;
|
||||
this.isActive = true;
|
||||
this.createUI();
|
||||
}
|
||||
|
|
@ -194,12 +202,36 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
clipboardResult = await this.copyCanvasToClipboard(canvas);
|
||||
}
|
||||
|
||||
// Update UI based on results
|
||||
let successMessage = 'Screenshot downloaded successfully!';
|
||||
if (this.copyToClipboard && clipboardResult && clipboardResult.success) {
|
||||
successMessage = 'Screenshot downloaded and copied to clipboard!';
|
||||
} else if (this.copyToClipboard && clipboardResult && !clipboardResult.success) {
|
||||
successMessage = `Screenshot downloaded successfully! Clipboard copy failed: ${clipboardResult.error}`;
|
||||
// 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 = `<div>${successMessage}</div>`;
|
||||
|
|
@ -238,7 +270,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
expect(result.success).toBe(true);
|
||||
expect(result.downloadSuccess).toBe(true);
|
||||
expect(result.clipboardResult.success).toBe(true);
|
||||
expect(result.message).toBe('Screenshot downloaded and copied to clipboard!');
|
||||
expect(result.message).toBe('Screenshot saved and copied to clipboard!');
|
||||
});
|
||||
|
||||
test('should capture with black background and copy to clipboard', async () => {
|
||||
|
|
@ -254,7 +286,7 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.clipboardResult.success).toBe(true);
|
||||
expect(result.message).toBe('Screenshot downloaded and copied to clipboard!');
|
||||
expect(result.message).toBe('Screenshot saved and copied to clipboard!');
|
||||
});
|
||||
|
||||
test('should capture with white background and copy to clipboard', async () => {
|
||||
|
|
@ -270,24 +302,28 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.clipboardResult.success).toBe(true);
|
||||
expect(result.message).toBe('Screenshot downloaded and copied to clipboard!');
|
||||
expect(result.message).toBe('Screenshot saved and copied to clipboard!');
|
||||
});
|
||||
|
||||
test('should capture without clipboard when disabled', async () => {
|
||||
await screenshotSelector.init('black', false);
|
||||
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 downloaded successfully!');
|
||||
expect(global.navigator.clipboard.write).not.toHaveBeenCalled();
|
||||
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 () => {
|
||||
// Mock clipboard failure
|
||||
global.navigator.clipboard.write.mockRejectedValue(new DOMException('Permission denied', 'NotAllowedError'));
|
||||
// Reset and mock clipboard failure
|
||||
global.navigator.clipboard = {
|
||||
write: jest.fn().mockRejectedValue(new DOMException('Permission denied', 'NotAllowedError'))
|
||||
};
|
||||
|
||||
await screenshotSelector.init('black', true);
|
||||
|
||||
|
|
@ -297,8 +333,8 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
expect(result.downloadSuccess).toBe(true);
|
||||
expect(result.clipboardResult.success).toBe(false);
|
||||
expect(result.clipboardResult.errorType).toBe('permission_denied');
|
||||
expect(result.message).toContain('Screenshot downloaded successfully!');
|
||||
expect(result.message).toContain('Clipboard copy failed');
|
||||
expect(result.message).toContain('Screenshot saved successfully!');
|
||||
expect(result.message).toContain('permission denied');
|
||||
});
|
||||
|
||||
test('should handle clipboard API unavailable scenario', async () => {
|
||||
|
|
@ -313,8 +349,8 @@ describe('Integration Scenarios - Clipboard with Different Backgrounds', () => {
|
|||
expect(result.downloadSuccess).toBe(true);
|
||||
expect(result.clipboardResult.success).toBe(false);
|
||||
expect(result.clipboardResult.errorType).toBe('api_unavailable');
|
||||
expect(result.message).toContain('Screenshot downloaded successfully!');
|
||||
expect(result.message).toContain('Clipboard copy failed');
|
||||
expect(result.message).toContain('Screenshot saved successfully!');
|
||||
expect(result.message).toContain('clipboard');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
637
tests/save-to-pc-clipboard-combinations.test.js
Normal file
637
tests/save-to-pc-clipboard-combinations.test.js
Normal file
|
|
@ -0,0 +1,637 @@
|
|||
/**
|
||||
* 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);
|
||||
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
|
||||
});
|
||||
});
|
||||
209
tests/validation-logic.test.js
Normal file
209
tests/validation-logic.test.js
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/**
|
||||
* Tests for output method validation logic
|
||||
*/
|
||||
|
||||
// Mock DOM elements and Chrome APIs
|
||||
const mockChrome = {
|
||||
storage: {
|
||||
sync: {
|
||||
get: jest.fn(),
|
||||
set: jest.fn()
|
||||
}
|
||||
},
|
||||
tabs: {
|
||||
query: jest.fn(),
|
||||
sendMessage: jest.fn()
|
||||
},
|
||||
runtime: {
|
||||
onMessage: {
|
||||
addListener: jest.fn()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
global.chrome = mockChrome;
|
||||
|
||||
describe('Output Method Validation', () => {
|
||||
let mockDocument;
|
||||
let clipboardToggle;
|
||||
let saveToPcToggle;
|
||||
let startBtn;
|
||||
let validationWarning;
|
||||
let validateOutputMethods;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mocks
|
||||
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' }
|
||||
};
|
||||
|
||||
// Mock document.getElementById
|
||||
const mockGetElementById = 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;
|
||||
}
|
||||
});
|
||||
|
||||
mockDocument = {
|
||||
getElementById: mockGetElementById,
|
||||
addEventListener: jest.fn()
|
||||
};
|
||||
|
||||
global.document = mockDocument;
|
||||
|
||||
// Define validation function (extracted from popup.js logic)
|
||||
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;
|
||||
};
|
||||
});
|
||||
|
||||
describe('Validation Logic', () => {
|
||||
test('should return true 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 return true 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 return true 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 return false 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('UI State Management', () => {
|
||||
test('should hide warning and enable button when validation passes', () => {
|
||||
clipboardToggle.checked = true;
|
||||
saveToPcToggle.checked = false;
|
||||
|
||||
validateOutputMethods();
|
||||
|
||||
expect(validationWarning.style.display).toBe('none');
|
||||
expect(startBtn.disabled).toBe(false);
|
||||
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', '');
|
||||
});
|
||||
|
||||
test('should show warning and disable button when validation fails', () => {
|
||||
clipboardToggle.checked = false;
|
||||
saveToPcToggle.checked = false;
|
||||
|
||||
validateOutputMethods();
|
||||
|
||||
expect(validationWarning.style.display).toBe('block');
|
||||
expect(startBtn.disabled).toBe(true);
|
||||
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', 'validation-warning');
|
||||
});
|
||||
|
||||
test('should update accessibility attributes correctly', () => {
|
||||
// Test valid state
|
||||
clipboardToggle.checked = true;
|
||||
saveToPcToggle.checked = true;
|
||||
validateOutputMethods();
|
||||
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', '');
|
||||
|
||||
// Reset mock
|
||||
startBtn.setAttribute.mockClear();
|
||||
|
||||
// Test invalid state
|
||||
clipboardToggle.checked = false;
|
||||
saveToPcToggle.checked = false;
|
||||
validateOutputMethods();
|
||||
expect(startBtn.setAttribute).toHaveBeenCalledWith('aria-describedby', 'validation-warning');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
test('should handle undefined checkbox states gracefully', () => {
|
||||
clipboardToggle.checked = undefined;
|
||||
saveToPcToggle.checked = true;
|
||||
|
||||
const result = validateOutputMethods();
|
||||
|
||||
// undefined should be falsy, so only saveToPc (true) should make it valid
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle null checkbox states gracefully', () => {
|
||||
clipboardToggle.checked = null;
|
||||
saveToPcToggle.checked = null;
|
||||
|
||||
const result = validateOutputMethods();
|
||||
|
||||
// Both null should be falsy, so validation should fail
|
||||
expect(result).toBeFalsy(); // Use toBeFalsy to handle null/false/undefined
|
||||
expect(validationWarning.style.display).toBe('block');
|
||||
expect(startBtn.disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue