refactor: avoid deallocing note controller until local propagation complete
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
||||
SNTag,
|
||||
ItemsClientInterface,
|
||||
SNNote,
|
||||
Deferred,
|
||||
SyncServiceInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||
import { NoteViewController } from './NoteViewController'
|
||||
@@ -17,10 +19,16 @@ describe('note view controller', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
application = {} as jest.Mocked<WebApplication>
|
||||
application.streamItems = jest.fn()
|
||||
application.streamItems = jest.fn().mockReturnValue(() => {})
|
||||
application.getPreference = jest.fn().mockReturnValue(true)
|
||||
application.noAccount = jest.fn().mockReturnValue(false)
|
||||
application.isNativeMobileWeb = jest.fn().mockReturnValue(false)
|
||||
|
||||
Object.defineProperty(application, 'items', { value: {} as jest.Mocked<ItemsClientInterface> })
|
||||
|
||||
Object.defineProperty(application, 'sync', { value: {} as jest.Mocked<SyncServiceInterface> })
|
||||
application.sync.sync = jest.fn().mockReturnValue(Promise.resolve())
|
||||
|
||||
componentManager = {} as jest.Mocked<SNComponentManager>
|
||||
componentManager.legacyGetDefaultEditor = jest.fn()
|
||||
Object.defineProperty(application, 'componentManager', { value: componentManager })
|
||||
@@ -80,4 +88,30 @@ describe('note view controller', () => {
|
||||
expect(controller['defaultTag']).toEqual(tag)
|
||||
expect(application.items.addTagToNote).toHaveBeenCalledWith(expect.anything(), tag, expect.anything())
|
||||
})
|
||||
|
||||
it('should wait until item finishes saving locally before deiniting', async () => {
|
||||
const note = {
|
||||
uuid: 'note-uuid',
|
||||
} as jest.Mocked<SNNote>
|
||||
|
||||
application.items.findItem = jest.fn().mockReturnValue(note)
|
||||
|
||||
const controller = new NoteViewController(application, note)
|
||||
await controller.initialize()
|
||||
|
||||
const changePromise = Deferred()
|
||||
|
||||
application.mutator.changeItem = jest.fn().mockReturnValue(changePromise.promise)
|
||||
|
||||
const savePromise = controller.saveAndAwaitLocalPropagation({ isUserModified: true, bypassDebouncer: true })
|
||||
controller.deinit()
|
||||
|
||||
expect(controller.dealloced).toEqual(false)
|
||||
|
||||
changePromise.resolve(true)
|
||||
await changePromise.promise
|
||||
await savePromise
|
||||
|
||||
expect(controller.dealloced).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
PrefKey,
|
||||
} from '@standardnotes/models'
|
||||
import { UuidString } from '@standardnotes/snjs'
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { removeFromArray, Deferred } from '@standardnotes/utils'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemViewControllerInterface } from './ItemViewControllerInterface'
|
||||
import { TemplateNoteViewControllerOptions } from './TemplateNoteViewControllerOptions'
|
||||
@@ -29,14 +29,16 @@ const NotePreviewCharLimit = 160
|
||||
export class NoteViewController implements ItemViewControllerInterface {
|
||||
public item!: SNNote
|
||||
public dealloced = false
|
||||
public isTemplateNote = false
|
||||
public runtimeId = `${Math.random()}`
|
||||
public needsInit = true
|
||||
|
||||
private innerValueChangeObservers: ((note: SNNote, source: PayloadEmitSource) => void)[] = []
|
||||
private disposers: (() => void)[] = []
|
||||
public isTemplateNote = false
|
||||
private saveTimeout?: ReturnType<typeof setTimeout>
|
||||
private defaultTagUuid: UuidString | undefined
|
||||
private defaultTag?: SNTag
|
||||
public runtimeId = `${Math.random()}`
|
||||
public needsInit = true
|
||||
private savingLocallyPromise = Deferred<void>()
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
@@ -57,7 +59,14 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
}
|
||||
|
||||
deinit(): void {
|
||||
void this.savingLocallyPromise.promise.then(() => {
|
||||
this.performDeinitSafely()
|
||||
})
|
||||
}
|
||||
|
||||
private performDeinitSafely(): void {
|
||||
this.dealloced = true
|
||||
|
||||
for (const disposer of this.disposers) {
|
||||
disposer()
|
||||
}
|
||||
@@ -184,6 +193,8 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
throw Error('NoteViewController not initialized')
|
||||
}
|
||||
|
||||
this.savingLocallyPromise = Deferred<void>()
|
||||
|
||||
if (this.saveTimeout) {
|
||||
clearTimeout(this.saveTimeout)
|
||||
}
|
||||
@@ -198,7 +209,13 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.saveTimeout = setTimeout(() => {
|
||||
void this.undebouncedSave({ ...params, onLocalPropagationComplete: resolve })
|
||||
void this.undebouncedSave({
|
||||
...params,
|
||||
onLocalPropagationComplete: () => {
|
||||
this.savingLocallyPromise.resolve()
|
||||
resolve()
|
||||
},
|
||||
})
|
||||
}, syncDebouceMs)
|
||||
})
|
||||
}
|
||||
@@ -262,10 +279,10 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
params.isUserModified,
|
||||
)
|
||||
|
||||
params.onLocalPropagationComplete?.()
|
||||
|
||||
void this.application.sync.sync().then(() => {
|
||||
params.onRemoteSyncComplete?.()
|
||||
})
|
||||
|
||||
params.onLocalPropagationComplete?.()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user