From 92be373a33641c1ee945846ae8cc8901aed17f0b Mon Sep 17 00:00:00 2001 From: minottic Date: Fri, 26 Jan 2024 14:29:15 +0100 Subject: [PATCH 1/3] Add button to load unsaved edits from localstorage --- .../add-content/add-content.component.html | 3 +++ .../core/add-content/add-content.component.ts | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/scilog/src/app/logbook/core/add-content/add-content.component.html b/scilog/src/app/logbook/core/add-content/add-content.component.html index db1f510f..c2f9ae94 100644 --- a/scilog/src/app/logbook/core/add-content/add-content.component.html +++ b/scilog/src/app/logbook/core/add-content/add-content.component.html @@ -4,6 +4,9 @@

{{ dialogTitle }}

+ diff --git a/scilog/src/app/logbook/core/add-content/add-content.component.ts b/scilog/src/app/logbook/core/add-content/add-content.component.ts index 98bb0348..08e2b171 100644 --- a/scilog/src/app/logbook/core/add-content/add-content.component.ts +++ b/scilog/src/app/logbook/core/add-content/add-content.component.ts @@ -48,6 +48,8 @@ export class AddContentComponent implements OnInit { prel_fileStorage: any[] = []; config: any = []; editor: any; + editStorageKey: string; + lastEdit: string; constructor( private dataService: AddContentService, @@ -102,6 +104,7 @@ export class AddContentComponent implements OnInit { if (this.message.id) { this.notification = this.message; + this.setLocalStorage(); this.notification.snippetType = 'edit'; this.notification.toDelete = false; this.liveFeedback = false; @@ -130,6 +133,11 @@ export class AddContentComponent implements OnInit { return; } + private setLocalStorage() { + this.editStorageKey = `${this.message.id}LastEdit`; + this.lastEdit = localStorage.getItem(this.editStorageKey); + } + onEditorReady(editor: any) { console.log(Array.from(editor.ui.componentFactory.names())); editor.ui.getEditableElement().parentElement.insertBefore( @@ -154,11 +162,13 @@ export class AddContentComponent implements OnInit { } if (this.notification.snippetType === 'edit' && this.contentChanged) this.notification.snippetType = 'paragraph'; + localStorage.removeItem(this.editStorageKey); this.prepareMessage(this.data); this.sendMessage(); }; onChange({ editor }: ChangeEvent) { + localStorage.setItem(this.editStorageKey, editor.getData()); // this.editorChange(editor); this.contentChanged = true; } @@ -168,7 +178,6 @@ export class AddContentComponent implements OnInit { if (typeof editor != "undefined") { const data = editor.getData(); this.prel_fileStorage = editor['prel_fileStorage']; - this.contentChanged = false; this.changeChain(data); } } @@ -240,6 +249,11 @@ export class AddContentComponent implements OnInit { } + loadLastUnsavedEdit() { + if (this.lastEdit) + this.editor.setData(this.lastEdit); + } + updateTags(tags: string[]) { this.tag = tags; this.contentChanged = true; @@ -250,6 +264,11 @@ export class AddContentComponent implements OnInit { this.metadataPanelExpanded = !this.metadataPanelExpanded; } + noUnsavedEditTooltip() { + if (!this.lastEdit || this.lastEdit === this.data) + return 'No unsaved edit in current session'; + } + ngOnDestroy(): void { this.sendEditDelitionMessage(); console.log("deleting add-content subscriptions") @@ -265,6 +284,7 @@ export class AddContentComponent implements OnInit { } } + @HostListener('window:beforeunload') @HostListener('window:unload') onUnload() { this.sendEditDelitionMessage(); From 9f88140cfa5f37cea5060b3230bb93c9419cbf8d Mon Sep 17 00:00:00 2001 From: minottic Date: Fri, 26 Jan 2024 14:32:05 +0100 Subject: [PATCH 2/3] Add tests --- .../add-content/add-content.component.spec.ts | 95 +++++++++++++------ 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts b/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts index 368b61a0..1108379f 100644 --- a/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts +++ b/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts @@ -118,14 +118,18 @@ describe('AddContentComponent', () => { expect(component.liveFeedback).toBe(false); expect(component.addButtonLabel).toBe("Done"); expect(component.sendMessage).toHaveBeenCalledTimes(1); + expect(component.editStorageKey).toEqual('6061d9a13587f37b851694d6LastEdit'); }); it('should get data from editor before sending', () => { component.setupComponent(); component.editor = jasmine.createSpyObj("component.editor", ["getData"]) + const localStorageSpy = spyOn(localStorage, 'removeItem'); + component.editStorageKey = '123LastEdit'; component.addContent(""); expect(component.editor.getData).toHaveBeenCalled(); - }) + expect(localStorageSpy).toHaveBeenCalledOnceWith('123LastEdit'); + }); it('should get edit data from editor before sending', () => { component.setupComponent(); @@ -135,7 +139,8 @@ describe('AddContentComponent', () => { component.addContent(""); expect(component.editor.getData).toHaveBeenCalled(); expect(component.notification.snippetType).toEqual('paragraph'); - }) + expect(component.editStorageKey).toEqual(undefined); + }); it('should prepare quote', () => { let quoted_snippet = { @@ -188,7 +193,7 @@ describe('AddContentComponent', () => { expect(component.metadataPanelExpanded).toBe(false); component.toggleMetadataPanel(); expect(component.metadataPanelExpanded).toBe(true); - }) + }); it('should update tags', () => { spyOn(component, 'changeChain') @@ -197,13 +202,13 @@ describe('AddContentComponent', () => { expect(component.contentChanged).toBe(true); expect(component.tag).toEqual(defaultTags); expect(component.changeChain).toHaveBeenCalledTimes(1); - }) + }); it('should send message', () => { spyOn(component['dataService'], 'changeMessage'); component.sendMessage(); expect(component['dataService'].changeMessage).toHaveBeenCalledWith(component.notification); - }) + }); it('should adjust figure HTML content for ckeditor', () => { let figureMockNoSize = '
'; @@ -223,8 +228,7 @@ describe('AddContentComponent', () => { component.data = figureMockSizeBefore; component.adjustContentForEditor(); expect(component.data).toBe(figureMockSizeAfter); - - }) + }); it('should adjust figure HTML content with links for ckeditor ', () => { @@ -239,8 +243,7 @@ describe('AddContentComponent', () => { component.data = figureMockSizeBefore; component.adjustContentForEditor(); expect(component.data).toBe(figureMockSizeAfter); - - }) + }); it('should extract notification with new files', () => { let figureMock = '
"'; @@ -254,8 +257,7 @@ describe('AddContentComponent', () => { expect(message.files[0].fileHash).toBeTruthy(); combineHtmlFigureHash(figureMockNoSrc, message.files[0].fileHash) expect(message.textcontent).toEqual(combineHtmlFigureHash(figureMockNoSrc, message.files[0].fileHash)); - - }) + }); it('should extract notification without files', () => { let figureMock = '
"' @@ -268,18 +270,16 @@ describe('AddContentComponent', () => { expect(message.files[0].fileHash).toBeTruthy(); combineHtmlFigureHash(figureMockNoSrc, message.files[0].fileHash) expect(message.textcontent).toEqual(combineHtmlFigureHash(figureMockNoSrc, message.files[0].fileHash)); - - }) + }); it('should extract links', () => { let linkMock = '

myFile.pdf

' let linkStorageMock = [{ fileHash: "myHash" }]; let message = extractNotificationMessage(linkMock, linkStorageMock); expect(message.files[0].fileHash).toEqual("myHash") - }) + }); it('should prepare subsnippets container for quotes', () => { - component.notification = notificationMock; component.prepareSubsnippetsQuoteContainer(); expect(component.notification.subsnippets[0].id).toBeFalsy(); @@ -291,21 +291,20 @@ describe('AddContentComponent', () => { expect(component.notification.subsnippets[0].defaultOrder).toBeFalsy(); expect(component.notification.subsnippets[0].subsnippets).toBeFalsy(); expect(component.notification.subsnippets[0].linkType).toBe(LinkType.QUOTE); - - }) + }); it('should send a message if notification is edit', () => { spyOn(component, 'sendMessage'); component.notification.snippetType = 'edit'; component.changeChain(notificationMock); expect(component.sendMessage).toHaveBeenCalledTimes(1); - }) + }); it('should not send messages if notification is not edit', () => { spyOn(component, 'sendMessage'); component.changeChain(notificationMock); expect(component.sendMessage).toHaveBeenCalledTimes(0); - }) + }); it('should mark for deletion and send message', () => { spyOn(component, 'sendMessage'); @@ -313,26 +312,66 @@ describe('AddContentComponent', () => { component['sendEditDelitionMessage'](); expect(component.sendMessage).toHaveBeenCalledTimes(1); expect(component.notification.toDelete).toEqual(true); - }) + }); it('should not mark for deletion and send message', () => { spyOn(component, 'sendMessage'); component.sendEditDelitionMessage(); expect(component.sendMessage).toHaveBeenCalledTimes(0); - }) + }); - it('should unset the logbook on unload', () => { - spyOn(component, 'sendEditDelitionMessage'); - const unloadEvent = new Event('unload'); - window.dispatchEvent(unloadEvent); - expect(component.sendEditDelitionMessage).toHaveBeenCalledTimes(1); - }) + ['unload', 'beforeunload'].forEach(t => { + it(`should unset the logbook on ${t}`, () => { + spyOn(component, 'sendEditDelitionMessage'); + const unloadEvent = new Event(t); + window.dispatchEvent(unloadEvent); + expect(component.sendEditDelitionMessage).toHaveBeenCalledTimes(1); + }); + }); it('should unset the logbook on destroy', () => { spyOn(component, 'sendEditDelitionMessage'); component.ngOnDestroy(); expect(component.sendEditDelitionMessage).toHaveBeenCalledTimes(1); - }) + }); + + [ + {input: undefined, output: 'No unsaved edit in current session'}, + {input: 'sameData', output: 'No unsaved edit in current session'}, + {input: 'edit', output: undefined}, + ].forEach((t, i) => { + it(`should test noUnsavedEditTooltip ${i}`, () => { + component.lastEdit = t.input; + if (t.input === 'sameData') component.data = t.input; + expect(component.noUnsavedEditTooltip()).toEqual(t.output); + }); + }); + + it('should test setLocalStorage', () => { + component.message = {id: 123}; + const localStorageSpy = spyOn(localStorage, 'getItem'); + component['setLocalStorage'](); + expect(localStorageSpy).toHaveBeenCalledOnceWith('123LastEdit'); + }); + + [undefined, 'edit'].forEach((t, i) => { + it(`should test loadLastUnsavedEdit ${i}`, () => { + component.lastEdit = t; + component.editor = jasmine.createSpyObj("component.editor", ["setData"]); + component.loadLastUnsavedEdit(); + expect(component.editor.setData).toHaveBeenCalledTimes(t? 1: 0); + if (t) + expect(component.editor.setData).toHaveBeenCalledOnceWith(t); + }); + }); + + it('should test onChange', () => { + component.editStorageKey = '123LastEdit'; + const editor = {getData: () => 'edit'}; + const localStorageSpy = spyOn(localStorage, 'setItem'); + component.onChange({editor: editor}); + expect(localStorageSpy).toHaveBeenCalledOnceWith('123LastEdit', 'edit'); + }); function combineHtmlFigureHash(figureMock: string[], hash: string) { return (figureMock[0] + ' title="' + hash + '"' + figureMock[1]); From c1301b99fbb16484d5b17a370851a0bf50a76eba Mon Sep 17 00:00:00 2001 From: minottic Date: Fri, 26 Jan 2024 15:28:36 +0100 Subject: [PATCH 3/3] Test on before unload hangs due to confirm dialog --- .../add-content/add-content.component.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts b/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts index 1108379f..112abc4d 100644 --- a/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts +++ b/scilog/src/app/logbook/core/add-content/add-content.component.spec.ts @@ -70,7 +70,7 @@ describe('AddContentComponent', () => { { provide: MAT_DIALOG_DATA, useValue: {} }, { provide: MatDialogRef, useValue: {} }, { provide: AddContentService, useClass: AddContentServiceMock }, - { provide: AppConfigService, useValue: { getConfig } } + { provide: AppConfigService, useValue: { getConfig } }, ], declarations: [AddContentComponent] }) @@ -320,13 +320,12 @@ describe('AddContentComponent', () => { expect(component.sendMessage).toHaveBeenCalledTimes(0); }); - ['unload', 'beforeunload'].forEach(t => { - it(`should unset the logbook on ${t}`, () => { - spyOn(component, 'sendEditDelitionMessage'); - const unloadEvent = new Event(t); - window.dispatchEvent(unloadEvent); - expect(component.sendEditDelitionMessage).toHaveBeenCalledTimes(1); - }); + + it('should unset the logbook on unload', () => { + spyOn(component, 'sendEditDelitionMessage'); + const unloadEvent = new Event('unload'); + window.dispatchEvent(unloadEvent); + expect(component.sendEditDelitionMessage).toHaveBeenCalledTimes(1); }); it('should unset the logbook on destroy', () => {