fix: lists and list items deleted on another device are now deleted locally
This commit is contained in:
@@ -123,6 +123,9 @@ describe('useListsStore', () => {
|
|||||||
|
|
||||||
it('creates a list locally, queues a sync entry, and remaps the id after a successful sync', async () => {
|
it('creates a list locally, queues a sync entry, and remaps the id after a successful sync', async () => {
|
||||||
listsApiMocks.createListApi.mockResolvedValueOnce({ id: 'server-id-1', name: 'Groceries' })
|
listsApiMocks.createListApi.mockResolvedValueOnce({ id: 'server-id-1', name: 'Groceries' })
|
||||||
|
// The chained pullFromServer() call needs to report the just-created list
|
||||||
|
// back, otherwise it would look like the server deleted it.
|
||||||
|
listsApiMocks.getListsApi.mockResolvedValueOnce([{ id: 'server-id-1', name: 'Groceries' }])
|
||||||
|
|
||||||
const store = useListsStore()
|
const store = useListsStore()
|
||||||
const localList = await store.createList('Groceries', [])
|
const localList = await store.createList('Groceries', [])
|
||||||
@@ -264,6 +267,55 @@ describe('useListsStore', () => {
|
|||||||
expect(store.listItems.find((item) => item.id === 'server-item-1')).toBeDefined()
|
expect(store.listItems.find((item) => item.id === 'server-item-1')).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('deletes a previously synced list locally when it is missing from the server', async () => {
|
||||||
|
const store = useListsStore()
|
||||||
|
await fakeDb.lists.put({ id: 'server-list-1', name: 'Groceries', pendingSync: false })
|
||||||
|
await fakeDb.listItems.put({
|
||||||
|
id: 'server-item-1',
|
||||||
|
list_id: 'server-list-1',
|
||||||
|
title: 'Milk',
|
||||||
|
pendingSync: false,
|
||||||
|
})
|
||||||
|
await store.refresh()
|
||||||
|
|
||||||
|
listsApiMocks.getListsApi.mockResolvedValueOnce([])
|
||||||
|
|
||||||
|
await store.pullFromServer()
|
||||||
|
|
||||||
|
expect(store.lists.find((list) => list.id === 'server-list-1')).toBeUndefined()
|
||||||
|
expect(store.listItems.find((item) => item.id === 'server-item-1')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deletes a previously synced list item locally when it is missing from the server', async () => {
|
||||||
|
const store = useListsStore()
|
||||||
|
await fakeDb.listItems.put({
|
||||||
|
id: 'server-item-2',
|
||||||
|
list_id: 'server-list-1',
|
||||||
|
title: 'Bread',
|
||||||
|
pendingSync: false,
|
||||||
|
})
|
||||||
|
await store.refresh()
|
||||||
|
|
||||||
|
listsApiMocks.getListItemsApi.mockResolvedValueOnce([])
|
||||||
|
|
||||||
|
await store.pullListItems('server-list-1')
|
||||||
|
|
||||||
|
expect(store.listItems.find((item) => item.id === 'server-item-2')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not delete a locally pending list even when it is missing from the server', async () => {
|
||||||
|
listsApiMocks.createListApi.mockImplementation(() => new Promise(() => {}))
|
||||||
|
|
||||||
|
const store = useListsStore()
|
||||||
|
const localList = await store.createList('Local only')
|
||||||
|
|
||||||
|
listsApiMocks.getListsApi.mockResolvedValueOnce([])
|
||||||
|
|
||||||
|
await store.pullFromServer()
|
||||||
|
|
||||||
|
expect(store.lists.find((list) => list.id === localList.id)).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('does not overwrite a locally pending list with stale server data', async () => {
|
it('does not overwrite a locally pending list with stale server data', async () => {
|
||||||
listsApiMocks.createListApi.mockImplementation(() => new Promise(() => {}))
|
listsApiMocks.createListApi.mockImplementation(() => new Promise(() => {}))
|
||||||
|
|
||||||
|
|||||||
@@ -369,6 +369,7 @@ export const useListsStore = defineStore('lists', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const serverLists = await getListsApi()
|
const serverLists = await getListsApi()
|
||||||
|
const serverListIds = new Set(serverLists.map((serverList) => serverList.id))
|
||||||
|
|
||||||
for (const serverList of serverLists) {
|
for (const serverList of serverLists) {
|
||||||
const existingList = await db.lists.get(serverList.id)
|
const existingList = await db.lists.get(serverList.id)
|
||||||
@@ -376,6 +377,23 @@ export const useListsStore = defineStore('lists', () => {
|
|||||||
await db.lists.put({ ...serverList, pendingSync: false })
|
await db.lists.put({ ...serverList, pendingSync: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lists that were already synced but are no longer reported by the
|
||||||
|
// server have been deleted there (e.g. from another device), so
|
||||||
|
// remove them locally too, along with their items. Lists that still
|
||||||
|
// have pending local changes (not yet synced, e.g. a not-yet-pushed
|
||||||
|
// "createList") are left alone since the server doesn't know about
|
||||||
|
// them yet.
|
||||||
|
const localLists = await db.lists.toArray()
|
||||||
|
for (const localList of localLists) {
|
||||||
|
if (!localList.pendingSync && !serverListIds.has(localList.id)) {
|
||||||
|
await db.lists.delete(localList.id)
|
||||||
|
const orphanedItems = await db.listItems.where('list_id').equals(localList.id).toArray()
|
||||||
|
for (const item of orphanedItems) {
|
||||||
|
await db.listItems.delete(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to load lists from server'
|
error.value = err instanceof Error ? err.message : 'Failed to load lists from server'
|
||||||
} finally {
|
} finally {
|
||||||
@@ -391,12 +409,25 @@ export const useListsStore = defineStore('lists', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const serverItems = await getListItemsApi(listId)
|
const serverItems = await getListItemsApi(listId)
|
||||||
|
const serverItemIds = new Set(serverItems.map((serverItem) => serverItem.id))
|
||||||
|
|
||||||
for (const serverItem of serverItems) {
|
for (const serverItem of serverItems) {
|
||||||
const existingItem = await db.listItems.get(serverItem.id)
|
const existingItem = await db.listItems.get(serverItem.id)
|
||||||
if (!existingItem || !existingItem.pendingSync) {
|
if (!existingItem || !existingItem.pendingSync) {
|
||||||
await db.listItems.put({ ...serverItem, pendingSync: false })
|
await db.listItems.put({ ...serverItem, pendingSync: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Items that were already synced but are no longer reported by the
|
||||||
|
// server for this list have been deleted there, so remove them
|
||||||
|
// locally too. Items with pending local changes are left alone since
|
||||||
|
// the server doesn't know about them yet.
|
||||||
|
const localItems = await db.listItems.where('list_id').equals(listId).toArray()
|
||||||
|
for (const localItem of localItems) {
|
||||||
|
if (!localItem.pendingSync && !serverItemIds.has(localItem.id)) {
|
||||||
|
await db.listItems.delete(localItem.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to load list items from server'
|
error.value = err instanceof Error ? err.message : 'Failed to load list items from server'
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user