From a97e9d4b3bbd8fb4330ad8c6fd44ee6738c695ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 04:01:19 +0000 Subject: [PATCH 1/2] Initial plan From e2152d9c1bf300b36eaf2e6b3cb7805b89dce9c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 04:07:49 +0000 Subject: [PATCH 2/2] Implement wish list feature with drag-and-drop sorting Co-authored-by: JoeProgrammer88 <7156063+JoeProgrammer88@users.noreply.github.com> --- css/styles.css | 90 +++++++++++++++++++++++++ index.html | 44 +++++++++++++ js/app.d.ts.map | 2 +- js/app.js | 130 +++++++++++++++++++++++++++++++++++++ js/storage.d.ts.map | 2 +- js/storage.js | 92 +++++++++++++++++++++++++- src/app.ts | 148 +++++++++++++++++++++++++++++++++++++++++- src/storage.ts | 65 ++++++++++++++++++- tests/storage.test.ts | 66 +++++++++++++++++++ 9 files changed, 632 insertions(+), 7 deletions(-) diff --git a/css/styles.css b/css/styles.css index 655468c..31e374a 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1477,3 +1477,93 @@ html, body { width: 100%; } } + +/* ======================== + Wish List + ======================== */ +.wishlist-container { + max-width: 800px; + margin: 0 auto; +} + +.wishlist-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.wish-list { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.wish-item { + display: flex; + align-items: center; + gap: 0.75rem; + background: #fff; + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 8px; + padding: 1rem; + cursor: grab; + transition: box-shadow 0.2s, opacity 0.2s; +} + +.wish-item:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.wish-item.dragging { + opacity: 0.5; + cursor: grabbing; +} + +.wish-item.drag-over { + border-color: var(--primary-color, #4CAF50); + box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); +} + +.wish-drag-handle { + font-size: 1.2rem; + color: #aaa; + cursor: grab; + user-select: none; + flex-shrink: 0; +} + +.wish-item-content { + flex: 1; + min-width: 0; +} + +.wish-item-title { + font-weight: 600; + font-size: 1rem; + margin-bottom: 0.25rem; + word-break: break-word; +} + +.wish-item-meta { + display: flex; + gap: 1rem; + flex-wrap: wrap; + align-items: center; + font-size: 0.875rem; + color: #666; +} + +.wish-item-price { + color: var(--primary-color, #4CAF50); + font-weight: 600; +} + +.wish-item-url { + color: #1976D2; + text-decoration: none; +} + +.wish-item-url:hover { + text-decoration: underline; +} diff --git a/index.html b/index.html index adf67d3..5638baa 100644 --- a/index.html +++ b/index.html @@ -65,6 +65,7 @@

📋 Task Manager

+ @@ -613,6 +614,49 @@

Add Reward

+ +
+
+
+

Wish List

+ +
+ +
+

No items in your wish list. Add one to get started!

+
+
+ + + +
+
diff --git a/js/app.d.ts.map b/js/app.d.ts.map index 8f48be1..ed511fe 100644 --- a/js/app.d.ts.map +++ b/js/app.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/js/app.js b/js/app.js index ea63d7c..c6fa106 100644 --- a/js/app.js +++ b/js/app.js @@ -10,6 +10,8 @@ class TaskManager { this.currentEditingFinanceId = null; this.currentEditingFinanceType = null; this.currentEditingRewardId = null; + this.currentEditingWishItemId = null; + this.dragSrcWishId = null; this.selectedDate = new Date(); this.tasksExpanded = false; this.emojis = [ @@ -102,6 +104,11 @@ class TaskManager { document.getElementById('rewardForm').addEventListener('submit', (e) => this.saveReward(e)); document.getElementById('cancelRewardBtn').addEventListener('click', () => this.closeRewardModal()); document.getElementById('deleteRewardBtn').addEventListener('click', () => this.deleteReward()); + // Wish List section + document.getElementById('addWishItemBtn').addEventListener('click', () => this.openWishItemModal()); + document.getElementById('wishItemForm').addEventListener('submit', (e) => this.saveWishItem(e)); + document.getElementById('cancelWishItemBtn').addEventListener('click', () => this.closeWishItemModal()); + document.getElementById('deleteWishItemBtn').addEventListener('click', () => this.deleteWishItem()); // Modal close buttons document.querySelectorAll('.close-btn').forEach(btn => { btn.addEventListener('click', (e) => { @@ -210,6 +217,9 @@ class TaskManager { else if (tabName === 'shop') { this.renderShop(); } + else if (tabName === 'wishlist') { + this.renderWishList(); + } else if (tabName === 'settings') { this.renderSettings(); } @@ -1514,6 +1524,126 @@ class TaskManager { } } // ======================== + // Wish List + // ======================== + renderWishList() { + const items = storage.getWishItems(); + const container = document.getElementById('wishList'); + if (items.length === 0) { + container.innerHTML = '

No items in your wish list. Add one to get started!

'; + return; + } + container.innerHTML = items.map(item => this.renderWishItem(item)).join(''); + container.querySelectorAll('.wish-item').forEach(el => { + el.addEventListener('dragstart', (e) => { + this.dragSrcWishId = el.dataset.wishId; + el.classList.add('dragging'); + e.dataTransfer.effectAllowed = 'move'; + }); + el.addEventListener('dragend', () => { + this.dragSrcWishId = null; + el.classList.remove('dragging'); + container.querySelectorAll('.wish-item').forEach(i => i.classList.remove('drag-over')); + }); + el.addEventListener('dragover', (e) => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + container.querySelectorAll('.wish-item').forEach(i => i.classList.remove('drag-over')); + el.classList.add('drag-over'); + }); + el.addEventListener('drop', (e) => { + e.preventDefault(); + const targetId = el.dataset.wishId; + if (this.dragSrcWishId && this.dragSrcWishId !== targetId) { + const allItems = storage.getWishItems(); + const srcIdx = allItems.findIndex(i => i.id === this.dragSrcWishId); + const tgtIdx = allItems.findIndex(i => i.id === targetId); + if (srcIdx !== -1 && tgtIdx !== -1) { + const reordered = [...allItems]; + const [moved] = reordered.splice(srcIdx, 1); + reordered.splice(tgtIdx, 0, moved); + storage.reorderWishItems(reordered.map(i => i.id)); + this.renderWishList(); + } + } + }); + el.querySelector('.edit-wish-btn').addEventListener('click', (e) => { + e.stopPropagation(); + this.openWishItemModal(el.dataset.wishId); + }); + }); + } + renderWishItem(item) { + const priceStr = item.price !== undefined && item.price !== null + ? `$${Number(item.price).toFixed(2)}` + : ''; + const urlStr = item.url + ? `🔗 View Listing` + : ''; + return ` +
+ +
+
${item.title}
+
+ ${priceStr} + ${urlStr} +
+
+ +
+ `; + } + openWishItemModal(itemId = null) { + this.currentEditingWishItemId = itemId; + const modal = document.getElementById('wishItemModal'); + const form = document.getElementById('wishItemForm'); + const deleteBtn = document.getElementById('deleteWishItemBtn'); + form.reset(); + deleteBtn.style.display = 'none'; + document.getElementById('wishItemModalTitle').textContent = itemId ? 'Edit Wish List Item' : 'Add Wish List Item'; + if (itemId) { + const item = storage.getWishItems().find(w => w.id === itemId); + if (item) { + document.getElementById('wishItemTitle').value = item.title; + document.getElementById('wishItemUrl').value = item.url || ''; + document.getElementById('wishItemPrice').value = + item.price !== undefined && item.price !== null ? String(item.price) : ''; + deleteBtn.style.display = 'block'; + } + } + modal.classList.add('active'); + } + closeWishItemModal() { + document.getElementById('wishItemModal').classList.remove('active'); + this.currentEditingWishItemId = null; + } + saveWishItem(e) { + e.preventDefault(); + const title = document.getElementById('wishItemTitle').value; + const url = document.getElementById('wishItemUrl').value.trim() || undefined; + const priceVal = document.getElementById('wishItemPrice').value; + const price = priceVal !== '' ? parseFloat(priceVal) : undefined; + const item = { title, url, price }; + if (this.currentEditingWishItemId) { + storage.updateWishItem(this.currentEditingWishItemId, item); + } + else { + storage.addWishItem(item); + } + this.closeWishItemModal(); + this.renderWishList(); + } + deleteWishItem() { + if (this.currentEditingWishItemId) { + if (confirm('Are you sure you want to delete this wish list item?')) { + storage.deleteWishItem(this.currentEditingWishItemId); + this.closeWishItemModal(); + this.renderWishList(); + } + } + } + // ======================== // Settings // ======================== renderSettings() { diff --git a/js/storage.d.ts.map b/js/storage.d.ts.map index 031aba3..326cdf2 100644 --- a/js/storage.d.ts.map +++ b/js/storage.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/js/storage.js b/js/storage.js index 7a63718..36bfa6a 100644 --- a/js/storage.js +++ b/js/storage.js @@ -43,7 +43,8 @@ export class StorageManager { }, settings: { tasksPerLevel: 30 - } + }, + wishList: [] }; localStorage.setItem(STORAGE_KEY, JSON.stringify(initialData)); } @@ -179,14 +180,46 @@ export class StorageManager { date: dateStr, timestamp: new Date().toISOString() }); - // Update habit streak + // Update habit streak based on fully-completed consecutive days const habit = data.habits.find(h => h.id === habitId); if (habit) { habit.lastCompletedDate = dateStr; - habit.streak = (habit.streak || 0) + 1; + habit.streak = this.calculateHabitStreak(habitId, habit.targetGoal, data.dailyHabitLogs); } this.saveData(data); } + calculateHabitStreak(habitId, targetGoal, logs) { + // Count completions per date for this habit + const habitLogs = logs.filter(l => l.habitId === habitId); + const countsByDate = {}; + for (const log of habitLogs) { + countsByDate[log.date] = (countsByDate[log.date] || 0) + 1; + } + // Get dates where fully completed (>= targetGoal), sorted most recent first + const completedDates = Object.keys(countsByDate) + .filter(date => countsByDate[date] >= targetGoal) + .sort() + .reverse(); + if (completedDates.length === 0) + return 0; + // Count consecutive days going backward from the most recent fully-completed day + let streak = 1; + let currentDate = completedDates[0]; + for (let i = 1; i < completedDates.length; i++) { + const [year, month, day] = currentDate.split('-').map(Number); + const prevDay = new Date(year, month - 1, day); + prevDay.setDate(prevDay.getDate() - 1); + const expectedDate = this.formatDate(prevDay); + if (completedDates[i] === expectedDate) { + streak++; + currentDate = completedDates[i]; + } + else { + break; + } + } + return streak; + } isHabitCompletedToday(habitId) { const data = this.getData(); const todayStr = this.formatDate(new Date()); @@ -391,6 +424,59 @@ export class StorageManager { const data = this.getData(); return data.purchaseHistory || []; } + // Wish List Management + addWishItem(item) { + const data = this.getData(); + if (!data.wishList) + data.wishList = []; + const newItem = { + ...item, + id: this.generateId(), + createdDate: new Date().toISOString(), + title: item.title || '', + order: data.wishList.length, + }; + data.wishList.push(newItem); + this.saveData(data); + return newItem; + } + updateWishItem(itemId, updates) { + const data = this.getData(); + if (!data.wishList) + data.wishList = []; + const item = data.wishList.find(w => w.id === itemId); + if (item) { + Object.assign(item, updates); + this.saveData(data); + } + return item; + } + deleteWishItem(itemId) { + const data = this.getData(); + if (!data.wishList) + data.wishList = []; + data.wishList = data.wishList.filter(w => w.id !== itemId); + // Re-index order values + data.wishList.forEach((w, idx) => { w.order = idx; }); + this.saveData(data); + } + getWishItems() { + const data = this.getData(); + if (!data.wishList) + return []; + return data.wishList.slice().sort((a, b) => a.order - b.order); + } + reorderWishItems(orderedIds) { + const data = this.getData(); + if (!data.wishList) + return; + orderedIds.forEach((id, idx) => { + const item = data.wishList.find(w => w.id === id); + if (item) + item.order = idx; + }); + this.saveData(data); + } // Points Management addPoints(amount, source) { const data = this.getData(); diff --git a/src/app.ts b/src/app.ts index c587ce5..040b66a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,7 @@ // Main Application Logic // ======================== -import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem } from './storage.js'; +import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem, WishItem } from './storage.js'; interface Activity { type: string; @@ -22,6 +22,8 @@ class TaskManager { currentEditingFinanceId: string | null = null; currentEditingFinanceType: string | null = null; currentEditingRewardId: string | null = null; + currentEditingWishItemId: string | null = null; + dragSrcWishId: string | null = null; selectedDate: Date = new Date(); tasksExpanded: boolean = false; emojis: string[] = [ @@ -126,6 +128,12 @@ class TaskManager { document.getElementById('cancelRewardBtn')!.addEventListener('click', () => this.closeRewardModal()); document.getElementById('deleteRewardBtn')!.addEventListener('click', () => this.deleteReward()); + // Wish List section + document.getElementById('addWishItemBtn')!.addEventListener('click', () => this.openWishItemModal()); + document.getElementById('wishItemForm')!.addEventListener('submit', (e) => this.saveWishItem(e)); + document.getElementById('cancelWishItemBtn')!.addEventListener('click', () => this.closeWishItemModal()); + document.getElementById('deleteWishItemBtn')!.addEventListener('click', () => this.deleteWishItem()); + // Modal close buttons document.querySelectorAll('.close-btn').forEach(btn => { btn.addEventListener('click', (e) => { @@ -238,6 +246,8 @@ class TaskManager { this.renderFinances(); } else if (tabName === 'shop') { this.renderShop(); + } else if (tabName === 'wishlist') { + this.renderWishList(); } else if (tabName === 'settings') { this.renderSettings(); } @@ -1711,6 +1721,142 @@ class TaskManager { } } + // ======================== + // Wish List + // ======================== + renderWishList(): void { + const items = storage.getWishItems(); + const container = document.getElementById('wishList')!; + + if (items.length === 0) { + container.innerHTML = '

No items in your wish list. Add one to get started!

'; + return; + } + + container.innerHTML = items.map(item => this.renderWishItem(item)).join(''); + + container.querySelectorAll('.wish-item').forEach(el => { + el.addEventListener('dragstart', (e) => { + this.dragSrcWishId = el.dataset.wishId!; + el.classList.add('dragging'); + e.dataTransfer!.effectAllowed = 'move'; + }); + el.addEventListener('dragend', () => { + this.dragSrcWishId = null; + el.classList.remove('dragging'); + container.querySelectorAll('.wish-item').forEach(i => i.classList.remove('drag-over')); + }); + el.addEventListener('dragover', (e) => { + e.preventDefault(); + e.dataTransfer!.dropEffect = 'move'; + container.querySelectorAll('.wish-item').forEach(i => i.classList.remove('drag-over')); + el.classList.add('drag-over'); + }); + el.addEventListener('drop', (e) => { + e.preventDefault(); + const targetId = el.dataset.wishId!; + if (this.dragSrcWishId && this.dragSrcWishId !== targetId) { + const allItems = storage.getWishItems(); + const srcIdx = allItems.findIndex(i => i.id === this.dragSrcWishId); + const tgtIdx = allItems.findIndex(i => i.id === targetId); + if (srcIdx !== -1 && tgtIdx !== -1) { + const reordered = [...allItems]; + const [moved] = reordered.splice(srcIdx, 1); + reordered.splice(tgtIdx, 0, moved); + storage.reorderWishItems(reordered.map(i => i.id)); + this.renderWishList(); + } + } + }); + el.querySelector('.edit-wish-btn')!.addEventListener('click', (e) => { + e.stopPropagation(); + this.openWishItemModal(el.dataset.wishId!); + }); + }); + } + + renderWishItem(item: WishItem): string { + const priceStr = item.price !== undefined && item.price !== null + ? `$${Number(item.price).toFixed(2)}` + : ''; + const urlStr = item.url + ? `🔗 View Listing` + : ''; + return ` +
+ +
+
${item.title}
+
+ ${priceStr} + ${urlStr} +
+
+ +
+ `; + } + + openWishItemModal(itemId: string | null = null): void { + this.currentEditingWishItemId = itemId; + const modal = document.getElementById('wishItemModal')!; + const form = document.getElementById('wishItemForm') as HTMLFormElement; + const deleteBtn = document.getElementById('deleteWishItemBtn') as HTMLElement; + + form.reset(); + deleteBtn.style.display = 'none'; + + document.getElementById('wishItemModalTitle')!.textContent = itemId ? 'Edit Wish List Item' : 'Add Wish List Item'; + + if (itemId) { + const item = storage.getWishItems().find(w => w.id === itemId); + if (item) { + (document.getElementById('wishItemTitle') as HTMLInputElement).value = item.title; + (document.getElementById('wishItemUrl') as HTMLInputElement).value = item.url || ''; + (document.getElementById('wishItemPrice') as HTMLInputElement).value = + item.price !== undefined && item.price !== null ? String(item.price) : ''; + deleteBtn.style.display = 'block'; + } + } + + modal.classList.add('active'); + } + + closeWishItemModal(): void { + document.getElementById('wishItemModal')!.classList.remove('active'); + this.currentEditingWishItemId = null; + } + + saveWishItem(e: Event): void { + e.preventDefault(); + + const title = (document.getElementById('wishItemTitle') as HTMLInputElement).value; + const url = (document.getElementById('wishItemUrl') as HTMLInputElement).value.trim() || undefined; + const priceVal = (document.getElementById('wishItemPrice') as HTMLInputElement).value; + const price = priceVal !== '' ? parseFloat(priceVal) : undefined; + + const item = { title, url, price }; + + if (this.currentEditingWishItemId) { + storage.updateWishItem(this.currentEditingWishItemId, item); + } else { + storage.addWishItem(item); + } + + this.closeWishItemModal(); + this.renderWishList(); + } + + deleteWishItem(): void { + if (this.currentEditingWishItemId) { + if (confirm('Are you sure you want to delete this wish list item?')) { + storage.deleteWishItem(this.currentEditingWishItemId); + this.closeWishItemModal(); + this.renderWishList(); + } + } + } + // ======================== // Settings // ======================== diff --git a/src/storage.ts b/src/storage.ts index d870247..2853b60 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -87,6 +87,15 @@ export interface Purchase { purchaseDate: string; } +export interface WishItem { + id: string; + title: string; + url?: string; + price?: number; + order: number; + createdDate: string; +} + export interface PointsBreakdown { tasks: number; projects: number; @@ -123,6 +132,7 @@ export interface AppData { categories: string[]; userStats: UserStats; settings: Settings; + wishList: WishItem[]; } export interface PurchaseResult { @@ -172,7 +182,8 @@ export class StorageManager { }, settings: { tasksPerLevel: 30 - } + }, + wishList: [] }; localStorage.setItem(STORAGE_KEY, JSON.stringify(initialData)); @@ -603,6 +614,58 @@ export class StorageManager { return data.purchaseHistory || []; } + // Wish List Management + addWishItem(item: Partial): WishItem { + const data = this.getData(); + if (!data.wishList) data.wishList = []; + const newItem: WishItem = { + ...item, + id: this.generateId(), + createdDate: new Date().toISOString(), + title: item.title || '', + order: data.wishList.length, + }; + data.wishList.push(newItem); + this.saveData(data); + return newItem; + } + + updateWishItem(itemId: string, updates: Partial): WishItem | undefined { + const data = this.getData(); + if (!data.wishList) data.wishList = []; + const item = data.wishList.find(w => w.id === itemId); + if (item) { + Object.assign(item, updates); + this.saveData(data); + } + return item; + } + + deleteWishItem(itemId: string): void { + const data = this.getData(); + if (!data.wishList) data.wishList = []; + data.wishList = data.wishList.filter(w => w.id !== itemId); + // Re-index order values + data.wishList.forEach((w, idx) => { w.order = idx; }); + this.saveData(data); + } + + getWishItems(): WishItem[] { + const data = this.getData(); + if (!data.wishList) return []; + return data.wishList.slice().sort((a, b) => a.order - b.order); + } + + reorderWishItems(orderedIds: string[]): void { + const data = this.getData(); + if (!data.wishList) return; + orderedIds.forEach((id, idx) => { + const item = data.wishList.find(w => w.id === id); + if (item) item.order = idx; + }); + this.saveData(data); + } + // Points Management addPoints(amount: number, source: string): void { const data = this.getData(); diff --git a/tests/storage.test.ts b/tests/storage.test.ts index 4d0fade..cb209cf 100644 --- a/tests/storage.test.ts +++ b/tests/storage.test.ts @@ -546,6 +546,72 @@ describe('StorageManager', () => { }); }); + // ======================== + // Wish List + // ======================== + describe('wish list management', () => { + it('should add a wish item', () => { + const item = storage.addWishItem({ title: 'New Laptop', url: 'https://example.com', price: 999.99 }); + expect(item.id).toBeDefined(); + expect(item.title).toBe('New Laptop'); + expect(item.url).toBe('https://example.com'); + expect(item.price).toBe(999.99); + expect(item.order).toBe(0); + expect(item.createdDate).toBeDefined(); + }); + + it('should add a wish item with only title', () => { + const item = storage.addWishItem({ title: 'Guitar' }); + expect(item.title).toBe('Guitar'); + expect(item.url).toBeUndefined(); + expect(item.price).toBeUndefined(); + }); + + it('should get all wish items sorted by order', () => { + storage.addWishItem({ title: 'Item A' }); + storage.addWishItem({ title: 'Item B' }); + storage.addWishItem({ title: 'Item C' }); + const items = storage.getWishItems(); + expect(items.length).toBe(3); + expect(items[0].order).toBeLessThanOrEqual(items[1].order); + expect(items[1].order).toBeLessThanOrEqual(items[2].order); + }); + + it('should update a wish item', () => { + const item = storage.addWishItem({ title: 'Old Title', price: 50 }); + const updated = storage.updateWishItem(item.id, { title: 'New Title', price: 75 }); + expect(updated?.title).toBe('New Title'); + expect(updated?.price).toBe(75); + }); + + it('should return undefined when updating non-existent wish item', () => { + const result = storage.updateWishItem('nonexistent', { title: 'Test' }); + expect(result).toBeUndefined(); + }); + + it('should delete a wish item and re-index order', () => { + storage.addWishItem({ title: 'Item A' }); + const itemB = storage.addWishItem({ title: 'Item B' }); + storage.addWishItem({ title: 'Item C' }); + storage.deleteWishItem(itemB.id); + const items = storage.getWishItems(); + expect(items.length).toBe(2); + expect(items[0].order).toBe(0); + expect(items[1].order).toBe(1); + }); + + it('should reorder wish items', () => { + const a = storage.addWishItem({ title: 'Item A' }); + const b = storage.addWishItem({ title: 'Item B' }); + const c = storage.addWishItem({ title: 'Item C' }); + storage.reorderWishItems([c.id, a.id, b.id]); + const items = storage.getWishItems(); + expect(items[0].title).toBe('Item C'); + expect(items[1].title).toBe('Item A'); + expect(items[2].title).toBe('Item B'); + }); + }); + // ======================== // Clear All Data // ========================