Lists and list items deleted on another device are now deleted locally #5
@@ -123,6 +123,9 @@ describe('useListsStore', () => {
|
||||
|
||||
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' })
|
||||
// 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 localList = await store.createList('Groceries', [])
|
||||
@@ -264,6 +267,55 @@ describe('useListsStore', () => {
|
||||
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 () => {
|
||||
listsApiMocks.createListApi.mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
|
||||
@@ -369,6 +369,7 @@ export const useListsStore = defineStore('lists', () => {
|
||||
|
||||
try {
|
||||
const serverLists = await getListsApi()
|
||||
const serverListIds = new Set(serverLists.map((serverList) => serverList.id))
|
||||
|
||||
for (const serverList of serverLists) {
|
||||
const existingList = await db.lists.get(serverList.id)
|
||||
@@ -376,6 +377,23 @@ export const useListsStore = defineStore('lists', () => {
|
||||
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) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to load lists from server'
|
||||
} finally {
|
||||
@@ -391,12 +409,25 @@ export const useListsStore = defineStore('lists', () => {
|
||||
|
||||
try {
|
||||
const serverItems = await getListItemsApi(listId)
|
||||
const serverItemIds = new Set(serverItems.map((serverItem) => serverItem.id))
|
||||
|
||||
for (const serverItem of serverItems) {
|
||||
const existingItem = await db.listItems.get(serverItem.id)
|
||||
if (!existingItem || !existingItem.pendingSync) {
|
||||
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) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to load list items from server'
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user