This commit is contained in:
2025-07-20 20:43:06 +02:00
parent 0abee5b794
commit 29592c7fc8
93 changed files with 23400 additions and 131 deletions

443
tests/test-migration.html Normal file
View File

@ -0,0 +1,443 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Migration Test</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Migration Functionality Test</h1>
<div style="margin: 20px 0;">
<button id="setupTestData" class="btn btn-secondary">Setup Test Data in localStorage</button>
<button id="showMigrationModal" class="btn btn-primary">Show Migration Modal</button>
<button id="clearTestData" class="btn btn-danger">Clear Test Data</button>
</div>
<div id="status" style="margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 8px;">
<h3>Status:</h3>
<p id="statusText">Ready to test migration functionality</p>
</div>
</div>
<!-- Data Migration Modal -->
<div id="migrationModal" class="modal" role="dialog" aria-labelledby="migrationTitle" aria-modal="true"
aria-hidden="true">
<div class="modal-content migration-modal-content">
<button class="close" aria-label="Close migration dialog">&times;</button>
<h2 id="migrationTitle">Import Local Bookmarks</h2>
<div class="migration-content">
<div class="migration-intro">
<p>We found <strong id="localBookmarkCount">0</strong> bookmarks stored locally in your browser.
Would you like to import them to your account?</p>
</div>
<div class="migration-options">
<h3>Import Options</h3>
<div class="form-group">
<label>
<input type="radio" name="migrationStrategy" value="merge" checked>
<strong>Merge with existing bookmarks</strong>
<div class="help-text">Add local bookmarks to your account, skipping duplicates</div>
</label>
</div>
<div class="form-group">
<label>
<input type="radio" name="migrationStrategy" value="replace">
<strong>Replace all bookmarks</strong>
<div class="help-text">Delete all existing bookmarks and import local ones</div>
</label>
</div>
</div>
<div class="migration-preview" id="migrationPreview" style="display: none;">
<h3>Migration Preview</h3>
<div class="migration-stats">
<div class="stat-item">
<span class="stat-label">Local bookmarks found:</span>
<span class="stat-value" id="localBookmarksCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Valid bookmarks:</span>
<span class="stat-value" id="validBookmarksCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Invalid bookmarks:</span>
<span class="stat-value" id="invalidBookmarksCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Duplicates to skip:</span>
<span class="stat-value" id="duplicatesCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">New bookmarks to import:</span>
<span class="stat-value" id="newBookmarksCount">0</span>
</div>
</div>
</div>
<div class="migration-warning" id="replaceWarning" style="display: none;">
<div class="warning-box">
<strong>⚠️ Warning:</strong> This will permanently delete all your existing bookmarks
and replace them with local bookmarks. This action cannot be undone.
</div>
</div>
<div class="migration-progress" id="migrationProgress" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="migrationProgressFill"></div>
</div>
<div class="progress-text" id="migrationProgressText">Preparing migration...</div>
</div>
<div class="migration-results" id="migrationResults" style="display: none;">
<h3>Migration Complete</h3>
<div class="results-summary">
<div class="result-item">
<span class="result-label">Successfully imported:</span>
<span class="result-value" id="migratedCount">0</span>
</div>
<div class="result-item">
<span class="result-label">Duplicates skipped:</span>
<span class="result-value" id="skippedCount">0</span>
</div>
<div class="result-item">
<span class="result-label">Validation errors:</span>
<span class="result-value" id="errorCount">0</span>
</div>
</div>
</div>
</div>
<div class="modal-actions">
<button id="startMigrationBtn" class="btn btn-primary" aria-label="Start bookmark migration">
Import Bookmarks
</button>
<button id="previewMigrationBtn" class="btn btn-secondary" aria-label="Preview migration">
Preview
</button>
<button id="skipMigrationBtn" class="btn btn-secondary" aria-label="Skip migration">
Skip for Now
</button>
<button id="closeMigrationBtn" class="btn btn-secondary" aria-label="Close migration dialog" style="display: none;">
Close
</button>
</div>
</div>
</div>
<script>
// Mock BookmarkManager for testing
class MigrationTester {
constructor() {
this.bookmarks = []; // Mock existing bookmarks
this.apiBaseUrl = '/api';
this.initializeMigrationModal();
}
// Copy the migration methods from the main script
checkForLocalBookmarks() {
try {
const localBookmarks = localStorage.getItem('bookmarks');
if (localBookmarks) {
const bookmarks = JSON.parse(localBookmarks);
if (Array.isArray(bookmarks) && bookmarks.length > 0) {
return bookmarks;
}
}
} catch (error) {
console.error('Error checking local bookmarks:', error);
}
return null;
}
showModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.style.display = 'block';
modal.setAttribute('aria-hidden', 'false');
}
}
hideModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
}
}
initializeMigrationModal() {
const startMigrationBtn = document.getElementById('startMigrationBtn');
const previewMigrationBtn = document.getElementById('previewMigrationBtn');
const skipMigrationBtn = document.getElementById('skipMigrationBtn');
const closeMigrationBtn = document.getElementById('closeMigrationBtn');
if (startMigrationBtn) {
startMigrationBtn.addEventListener('click', () => this.startMigration());
}
if (previewMigrationBtn) {
previewMigrationBtn.addEventListener('click', () => this.previewMigration());
}
if (skipMigrationBtn) {
skipMigrationBtn.addEventListener('click', () => this.skipMigration());
}
if (closeMigrationBtn) {
closeMigrationBtn.addEventListener('click', () => this.closeMigration());
}
// Strategy change handler
const strategyRadios = document.querySelectorAll('input[name="migrationStrategy"]');
strategyRadios.forEach(radio => {
radio.addEventListener('change', () => this.handleStrategyChange());
});
// Close modal handlers
const closeButtons = document.querySelectorAll('.close');
closeButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
const modal = e.target.closest('.modal');
if (modal) {
this.hideModal(modal.id);
}
});
});
}
handleStrategyChange() {
const selectedStrategy = document.querySelector('input[name="migrationStrategy"]:checked')?.value;
const warningElement = document.getElementById('replaceWarning');
if (warningElement) {
warningElement.style.display = selectedStrategy === 'replace' ? 'block' : 'none';
}
}
previewMigration() {
const localBookmarks = this.checkForLocalBookmarks();
if (!localBookmarks) {
alert('No local bookmarks found to migrate.');
return;
}
const selectedStrategy = document.querySelector('input[name="migrationStrategy"]:checked')?.value || 'merge';
try {
// Show preview section
const previewElement = document.getElementById('migrationPreview');
if (previewElement) {
previewElement.style.display = 'block';
}
// Validate local bookmarks
const validationResult = this.validateLocalBookmarks(localBookmarks);
// Update preview stats
document.getElementById('localBookmarksCount').textContent = localBookmarks.length;
document.getElementById('validBookmarksCount').textContent = validationResult.valid.length;
document.getElementById('invalidBookmarksCount').textContent = validationResult.invalid.length;
document.getElementById('duplicatesCount').textContent = '0'; // Mock
document.getElementById('newBookmarksCount').textContent = validationResult.valid.length;
} catch (error) {
console.error('Preview migration error:', error);
alert('Error previewing migration: ' + error.message);
}
}
validateLocalBookmarks(localBookmarks) {
const valid = [];
const invalid = [];
localBookmarks.forEach((bookmark, index) => {
const errors = [];
if (!bookmark.title || bookmark.title.trim().length === 0) {
errors.push('Missing title');
}
if (!bookmark.url || bookmark.url.trim().length === 0) {
errors.push('Missing URL');
}
if (bookmark.url) {
try {
new URL(bookmark.url);
} catch (e) {
errors.push('Invalid URL format');
}
}
if (errors.length === 0) {
valid.push({
title: bookmark.title.trim(),
url: bookmark.url.trim(),
folder: bookmark.folder || '',
add_date: bookmark.addDate || bookmark.add_date || new Date(),
last_modified: bookmark.lastModified || bookmark.last_modified,
icon: bookmark.icon || bookmark.favicon,
status: bookmark.status || 'unknown'
});
} else {
invalid.push({
index,
bookmark,
errors
});
}
});
return { valid, invalid };
}
startMigration() {
alert('Migration would start here. This is a test environment - no actual API call will be made.');
// Mock progress
this.showMigrationProgress();
this.updateMigrationProgress(0, 'Preparing migration...');
setTimeout(() => {
this.updateMigrationProgress(50, 'Validating bookmarks...');
setTimeout(() => {
this.updateMigrationProgress(100, 'Migration completed!');
setTimeout(() => {
this.showMigrationResults({
summary: {
successfullyMigrated: 3,
duplicatesSkipped: 1,
invalidBookmarks: 1
}
});
}, 1000);
}, 1000);
}, 1000);
}
showMigrationProgress() {
const progressElement = document.getElementById('migrationProgress');
if (progressElement) {
progressElement.style.display = 'block';
}
const startBtn = document.getElementById('startMigrationBtn');
const previewBtn = document.getElementById('previewMigrationBtn');
const skipBtn = document.getElementById('skipMigrationBtn');
if (startBtn) startBtn.style.display = 'none';
if (previewBtn) previewBtn.style.display = 'none';
if (skipBtn) skipBtn.style.display = 'none';
}
updateMigrationProgress(percentage, message) {
const progressFill = document.getElementById('migrationProgressFill');
const progressText = document.getElementById('migrationProgressText');
if (progressFill) {
progressFill.style.width = `${percentage}%`;
}
if (progressText) {
progressText.textContent = message;
}
}
showMigrationResults(result) {
const resultsElement = document.getElementById('migrationResults');
if (resultsElement) {
resultsElement.style.display = 'block';
}
const summary = result.summary;
document.getElementById('migratedCount').textContent = summary.successfullyMigrated || 0;
document.getElementById('skippedCount').textContent = summary.duplicatesSkipped || 0;
document.getElementById('errorCount').textContent = summary.invalidBookmarks || 0;
const closeBtn = document.getElementById('closeMigrationBtn');
if (closeBtn) {
closeBtn.style.display = 'inline-block';
}
const progressElement = document.getElementById('migrationProgress');
if (progressElement) {
progressElement.style.display = 'none';
}
}
skipMigration() {
const confirmed = confirm('Are you sure you want to skip the migration?');
if (confirmed) {
this.hideModal('migrationModal');
}
}
closeMigration() {
this.hideModal('migrationModal');
}
showMigrationModalIfNeeded() {
const localBookmarks = this.checkForLocalBookmarks();
if (localBookmarks && localBookmarks.length > 0) {
const countElement = document.getElementById('localBookmarkCount');
if (countElement) {
countElement.textContent = localBookmarks.length;
}
this.showModal('migrationModal');
}
}
}
// Initialize tester
const tester = new MigrationTester();
// Test controls
document.getElementById('setupTestData').addEventListener('click', () => {
const testBookmarks = [
{
title: "Test Bookmark 1",
url: "https://example.com",
folder: "Test Folder",
addDate: new Date().toISOString(),
icon: "https://example.com/favicon.ico",
status: "unknown"
},
{
title: "Test Bookmark 2",
url: "https://google.com",
folder: "",
addDate: new Date().toISOString(),
status: "unknown"
},
{
title: "Invalid Bookmark",
url: "not-a-valid-url",
folder: "Test Folder"
},
{
title: "GitHub",
url: "https://github.com",
folder: "Development",
addDate: new Date().toISOString(),
status: "valid"
}
];
localStorage.setItem('bookmarks', JSON.stringify(testBookmarks));
document.getElementById('statusText').textContent = `Setup complete! Added ${testBookmarks.length} test bookmarks to localStorage.`;
});
document.getElementById('showMigrationModal').addEventListener('click', () => {
tester.showMigrationModalIfNeeded();
});
document.getElementById('clearTestData').addEventListener('click', () => {
localStorage.removeItem('bookmarks');
document.getElementById('statusText').textContent = 'Test data cleared from localStorage.';
});
</script>
</body>
</html>