From 3951d6daaf2a9a976fa402b07cb1b7b807c952c0 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Thu, 22 Jun 2023 15:01:50 +0800 Subject: [PATCH 01/14] Add title-editor.component, title-editor.module --- .../title-editor/title-editor.component.css | 3 + .../title-editor/title-editor.component.html | 26 ++++ .../title-editor/title-editor.component.ts | 139 ++++++++++++++++++ .../title-editor/title-editor.module.ts | 11 ++ 4 files changed, 179 insertions(+) create mode 100644 src/app/shared/title-editor/title-editor.component.css create mode 100644 src/app/shared/title-editor/title-editor.component.html create mode 100644 src/app/shared/title-editor/title-editor.component.ts create mode 100644 src/app/shared/title-editor/title-editor.module.ts diff --git a/src/app/shared/title-editor/title-editor.component.css b/src/app/shared/title-editor/title-editor.component.css new file mode 100644 index 000000000..53a4bc5e4 --- /dev/null +++ b/src/app/shared/title-editor/title-editor.component.css @@ -0,0 +1,3 @@ +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/shared/title-editor/title-editor.component.html b/src/app/shared/title-editor/title-editor.component.html new file mode 100644 index 000000000..1c4ce4eeb --- /dev/null +++ b/src/app/shared/title-editor/title-editor.component.html @@ -0,0 +1,26 @@ +
+ + + + Title required. + + + Title cannot exceed {{ maxLength }} characters. + + + {{ maxLength - titleField.value?.length }} characters remaining. + + +
\ No newline at end of file diff --git a/src/app/shared/title-editor/title-editor.component.ts b/src/app/shared/title-editor/title-editor.component.ts new file mode 100644 index 000000000..7bdc8569f --- /dev/null +++ b/src/app/shared/title-editor/title-editor.component.ts @@ -0,0 +1,139 @@ +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { AbstractControl, FormGroup, Validators } from '@angular/forms'; +import { UndoRedo } from '../../core/models/undoredo.model'; + +const ISSUE_BODY_SIZE_LIMIT = 256; + +type textEntry = { + text: string; + selectStart: number; + selectEnd: number; +}; + +@Component({ + selector: 'app-title-editor', + templateUrl: './title-editor.component.html', + styleUrls: ['./title-editor.component.css'] +}) +export class TitleEditorComponent implements OnInit { + + constructor() {} + + @Input() titleField: AbstractControl; // Compulsory Input + @Input() titleForm: FormGroup; // Compulsory Input + @Input() id: string; // Compulsory Input + + @Input() initialTitle?: string; + placeholderText = 'Title'; + + @ViewChild('titleInput', { static: true }) titleInput: ElementRef; + + maxLength = ISSUE_BODY_SIZE_LIMIT; + + history: UndoRedo; + + ngOnInit() { + if (this.initialTitle !== undefined) { + this.titleField.setValue(this.initialTitle); + } + + if (this.titleField === undefined || this.titleForm === undefined || this.id === undefined) { + throw new Error("Title Editor's compulsory properties are not defined."); + } + + this.titleField.setValidators([Validators.required, Validators.maxLength(this.maxLength)]); + this.history = new UndoRedo( + 75, + () => { + return { + text: this.titleInput.nativeElement.value, + selectStart: this.titleInput.nativeElement.selectionStart, + selectEnd: this.titleInput.nativeElement.selectionEnd + }; + }, + 500 + ); + } + + onKeyPress(event: KeyboardEvent) { + if (this.isUndo(event)) { + event.preventDefault(); + this.undo(); + return; + } else if (this.isRedo(event)) { + this.redo(); + event.preventDefault(); + return; + } + } + + onPaste(event: ClipboardEvent) { + // the text area is not changed at this point + this.history.forceSave(null, true, false); + } + + handleBeforeInputChange(event: InputEvent): void { + switch (event.inputType) { + case 'historyUndo': + case 'historyRedo': + // ignore these events that doesn't modify the text + event.preventDefault(); + break; + case 'insertFromPaste': + // paste events will be handled exclusively by onPaste + break; + + default: + this.history.updateBeforeChange(); + } + } + + handleInputChange(event: InputEvent): void { + switch (event.inputType) { + case 'historyUndo': + case 'historyRedo': + // ignore these events that doesn't modify the text + event.preventDefault(); + break; + case 'insertFromPaste': + // paste events will be handled exclusively by onPaste + break; + + default: + this.history.createDelayedSave(); + } + } + + private undo(): void { + const entry = this.history.undo(); + if (entry === null) { + return; + } + this.titleField.setValue(entry.text); + this.titleInput.nativeElement.setSelectionRange(entry.selectStart, entry.selectEnd); + } + + private redo(): void { + const entry = this.history.redo(); + if (entry === null) { + return; + } + this.titleInput.nativeElement.value = entry.text; + this.titleInput.nativeElement.setSelectionRange(entry.selectStart, entry.selectEnd); + } + + private isUndo(event: KeyboardEvent) { + // prevents undo from firing when ctrl shift z is pressed + if (navigator.platform.indexOf('Mac') === 0) { + return event.metaKey && event.code === 'KeyZ' && !event.shiftKey; + } + return event.ctrlKey && event.code === 'KeyZ' && !event.shiftKey; + } + + private isRedo(event: KeyboardEvent) { + if (navigator.platform.indexOf('Mac') === 0) { + return event.metaKey && event.shiftKey && event.code === 'KeyZ'; + } + return (event.ctrlKey && event.shiftKey && event.code === 'KeyZ') || (event.ctrlKey && event.code === 'KeyY'); + } +} diff --git a/src/app/shared/title-editor/title-editor.module.ts b/src/app/shared/title-editor/title-editor.module.ts new file mode 100644 index 000000000..b63883771 --- /dev/null +++ b/src/app/shared/title-editor/title-editor.module.ts @@ -0,0 +1,11 @@ +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { SharedModule } from '../shared.module'; +import { TitleEditorComponent } from './title-editor.component'; + +@NgModule({ + imports: [SharedModule], + declarations: [TitleEditorComponent], + exports: [TitleEditorComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class TitleEditorModule {} From 9429a7e9f6b9436996ad63058ed39b8083277b23 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Thu, 22 Jun 2023 15:04:45 +0800 Subject: [PATCH 02/14] Modify existing titles to use title-editor --- .../new-issue/new-issue.component.css | 2 +- .../new-issue/new-issue.component.html | 12 ++--- .../new-issue/new-issue.component.ts | 2 +- .../phase-bug-reporting.module.ts | 2 + .../shared/issue/issue-components.module.ts | 9 +++- .../shared/issue/title/title.component.css | 4 ++ .../shared/issue/title/title.component.html | 48 ++++++++++--------- src/app/shared/issue/title/title.component.ts | 2 +- .../title-editor/title-editor.component.css | 2 +- .../title-editor/title-editor.component.html | 2 +- 10 files changed, 50 insertions(+), 35 deletions(-) diff --git a/src/app/phase-bug-reporting/new-issue/new-issue.component.css b/src/app/phase-bug-reporting/new-issue/new-issue.component.css index dfd602a10..c7bd63c26 100644 --- a/src/app/phase-bug-reporting/new-issue/new-issue.component.css +++ b/src/app/phase-bug-reporting/new-issue/new-issue.component.css @@ -3,7 +3,7 @@ margin: 0 auto; } -mat-form-field { +app-title-editor { width: 100%; } diff --git a/src/app/phase-bug-reporting/new-issue/new-issue.component.html b/src/app/phase-bug-reporting/new-issue/new-issue.component.html index 5af3d8332..774243899 100644 --- a/src/app/phase-bug-reporting/new-issue/new-issue.component.html +++ b/src/app/phase-bug-reporting/new-issue/new-issue.component.html @@ -4,12 +4,12 @@

New Issue

- - - Title required. - Title cannot exceed 256 characters. - {{ 256 - title.value?.length }} characters remaining. - + +
- - - Title is required. - Title cannot exceed 256 characters. - - {{ 256 - issueTitleForm.get('title').value?.length }} characters remaining. - - - - - +
+ + + +
+ + +
+
diff --git a/src/app/shared/issue/title/title.component.ts b/src/app/shared/issue/title/title.component.ts index ef9a9c5cf..73c40a1d4 100644 --- a/src/app/shared/issue/title/title.component.ts +++ b/src/app/shared/issue/title/title.component.ts @@ -47,7 +47,7 @@ export class TitleComponent implements OnInit { ngOnInit() { this.issueTitleForm = this.formBuilder.group({ - title: new FormControl('', [Validators.required, Validators.maxLength(256)]) + title: [''] }); // Build the loading service spinner this.loadingService diff --git a/src/app/shared/title-editor/title-editor.component.css b/src/app/shared/title-editor/title-editor.component.css index 53a4bc5e4..c7acb4bf6 100644 --- a/src/app/shared/title-editor/title-editor.component.css +++ b/src/app/shared/title-editor/title-editor.component.css @@ -1,3 +1,3 @@ mat-form-field { width: 100%; -} \ No newline at end of file +} diff --git a/src/app/shared/title-editor/title-editor.component.html b/src/app/shared/title-editor/title-editor.component.html index 1c4ce4eeb..a5fdf1575 100644 --- a/src/app/shared/title-editor/title-editor.component.html +++ b/src/app/shared/title-editor/title-editor.component.html @@ -23,4 +23,4 @@ {{ maxLength - titleField.value?.length }} characters remaining. - \ No newline at end of file + From e09cca00a788216f3ef1100b8a147585d8e1c4c5 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Thu, 22 Jun 2023 15:42:45 +0800 Subject: [PATCH 03/14] Add title-editor.component.spec.ts for testing --- .../title-editor.component.spec.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/app/shared/title-editor/title-editor.component.spec.ts diff --git a/tests/app/shared/title-editor/title-editor.component.spec.ts b/tests/app/shared/title-editor/title-editor.component.spec.ts new file mode 100644 index 000000000..d958f05f4 --- /dev/null +++ b/tests/app/shared/title-editor/title-editor.component.spec.ts @@ -0,0 +1,78 @@ +import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, FormGroup, FormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { Apollo } from 'apollo-angular'; + +import { TitleEditorComponent } from '../../../../src/app/shared/title-editor/title-editor.component' +import { SharedModule } from '../../../../src/app/shared/shared.module'; + +describe('CommentEditor', () => { + let fixture: ComponentFixture; + let debugElement: DebugElement; + let component: TitleEditorComponent; + + const TEST_INITIAL_TITLE = 'abc'; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TitleEditorComponent], + imports: [FormsModule, SharedModule, BrowserAnimationsModule], + providers: [Apollo], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }).compileComponents(); + + fixture = TestBed.createComponent(TitleEditorComponent); + debugElement = fixture.debugElement; + component = fixture.componentInstance; + + // initialize compulsory inputs + const titleField: FormControl = new FormControl(''); + const titleForm: FormGroup = new FormGroup({ + title: titleField + }); + const id = 'title'; + + // manually inject inputs into the component + component.titleField = titleField; + component.titleForm = titleForm; + component.id = id; + }); + + describe('text input box', () => { + it('should render', () => { + fixture.detectChanges(); + + const textBoxDe: DebugElement = debugElement.query(By.css('input')); + expect(textBoxDe).toBeTruthy(); + }); + + it('should contain an empty string if no initial description is provided', () => { + fixture.detectChanges(); + + const textBox: any = debugElement.query(By.css('input')).nativeElement; + expect(textBox.value).toEqual(''); + }); + + it('should contain an initial description if one is provided', () => { + component.initialTitle = TEST_INITIAL_TITLE; + fixture.detectChanges(); + + const textBox: any = debugElement.query(By.css('input')).nativeElement; + expect(textBox.value).toEqual(TEST_INITIAL_TITLE); + }); + + it('should allow users to input text', async () => { + fixture.detectChanges(); + + const textBox: any = debugElement.query(By.css('input')).nativeElement; + textBox.value = '123'; + textBox.dispatchEvent(new Event('input')); + + fixture.whenStable().then(() => { + expect(textBox.value).toEqual('123'); + }); + }); + }); +}); From 514f8eb65b8cd8f0f1a3af3caf858f5cd22aaaba Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Thu, 22 Jun 2023 15:56:45 +0800 Subject: [PATCH 04/14] Fix lint error --- tests/app/shared/title-editor/title-editor.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/app/shared/title-editor/title-editor.component.spec.ts b/tests/app/shared/title-editor/title-editor.component.spec.ts index d958f05f4..6c93cc696 100644 --- a/tests/app/shared/title-editor/title-editor.component.spec.ts +++ b/tests/app/shared/title-editor/title-editor.component.spec.ts @@ -5,8 +5,8 @@ import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Apollo } from 'apollo-angular'; -import { TitleEditorComponent } from '../../../../src/app/shared/title-editor/title-editor.component' import { SharedModule } from '../../../../src/app/shared/shared.module'; +import { TitleEditorComponent } from '../../../../src/app/shared/title-editor/title-editor.component'; describe('CommentEditor', () => { let fixture: ComponentFixture; From b3c173a4356bb80096585469e08c08008782bb58 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Fri, 23 Jun 2023 11:03:18 +0800 Subject: [PATCH 05/14] Remove unused classes --- src/app/shared/title-editor/title-editor.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/title-editor/title-editor.component.ts b/src/app/shared/title-editor/title-editor.component.ts index 7bdc8569f..e32e45027 100644 --- a/src/app/shared/title-editor/title-editor.component.ts +++ b/src/app/shared/title-editor/title-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; import { AbstractControl, FormGroup, Validators } from '@angular/forms'; import { UndoRedo } from '../../core/models/undoredo.model'; From 615632e20169189b794502a69d760970f3811ecc Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Fri, 23 Jun 2023 11:05:58 +0800 Subject: [PATCH 06/14] Remove Apollo --- tests/app/shared/title-editor/title-editor.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/app/shared/title-editor/title-editor.component.spec.ts b/tests/app/shared/title-editor/title-editor.component.spec.ts index 6c93cc696..d972fd692 100644 --- a/tests/app/shared/title-editor/title-editor.component.spec.ts +++ b/tests/app/shared/title-editor/title-editor.component.spec.ts @@ -3,7 +3,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormControl, FormGroup, FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Apollo } from 'apollo-angular'; import { SharedModule } from '../../../../src/app/shared/shared.module'; import { TitleEditorComponent } from '../../../../src/app/shared/title-editor/title-editor.component'; @@ -19,7 +18,6 @@ describe('CommentEditor', () => { TestBed.configureTestingModule({ declarations: [TitleEditorComponent], imports: [FormsModule, SharedModule, BrowserAnimationsModule], - providers: [Apollo], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); From 3af1542043476ad1aba1a60435cb6312063d72b5 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Fri, 23 Jun 2023 11:52:43 +0800 Subject: [PATCH 07/14] Shift `title-editor` component under shared/issue --- .../phase-bug-reporting/phase-bug-reporting.module.ts | 2 -- src/app/shared/issue/issue-components.module.ts | 5 +++-- .../title-editor/title-editor.component.css | 0 .../title-editor/title-editor.component.html | 1 - .../title-editor/title-editor.component.ts | 11 +++-------- src/app/shared/title-editor/title-editor.module.ts | 11 ----------- .../title-editor/title-editor.component.spec.ts | 2 +- 7 files changed, 7 insertions(+), 25 deletions(-) rename src/app/shared/{ => issue}/title-editor/title-editor.component.css (100%) rename src/app/shared/{ => issue}/title-editor/title-editor.component.html (96%) rename src/app/shared/{ => issue}/title-editor/title-editor.component.ts (91%) delete mode 100644 src/app/shared/title-editor/title-editor.module.ts diff --git a/src/app/phase-bug-reporting/phase-bug-reporting.module.ts b/src/app/phase-bug-reporting/phase-bug-reporting.module.ts index bcee6f27c..ead3fac40 100644 --- a/src/app/phase-bug-reporting/phase-bug-reporting.module.ts +++ b/src/app/phase-bug-reporting/phase-bug-reporting.module.ts @@ -5,7 +5,6 @@ import { IssueTablesModule } from '../shared/issue-tables/issue-tables.module'; import { IssueComponentsModule } from '../shared/issue/issue-components.module'; import { LabelDropdownModule } from '../shared/label-dropdown/label-dropdown.module'; import { SharedModule } from '../shared/shared.module'; -import { TitleEditorModule } from '../shared/title-editor/title-editor.module'; import { ViewIssueModule } from '../shared/view-issue/view-issue.module'; import { IssueComponent } from './issue/issue.component'; import { NewIssueComponent } from './new-issue/new-issue.component'; @@ -17,7 +16,6 @@ import { PhaseBugReportingComponent } from './phase-bug-reporting.component'; PhaseBugReportingRoutingModule, SharedModule, IssueComponentsModule, - TitleEditorModule, CommentEditorModule, ViewIssueModule, MarkdownModule.forChild(), diff --git a/src/app/shared/issue/issue-components.module.ts b/src/app/shared/issue/issue-components.module.ts index d16d00e30..cecacac89 100644 --- a/src/app/shared/issue/issue-components.module.ts +++ b/src/app/shared/issue/issue-components.module.ts @@ -4,7 +4,6 @@ import { MarkdownModule } from 'ngx-markdown'; import { NgxMatSelectSearchModule } from 'ngx-mat-select-search'; import { CommentEditorModule } from '../comment-editor/comment-editor.module'; import { SharedModule } from '../shared.module'; -import { TitleEditorModule } from '../title-editor/title-editor.module'; import { AssigneeComponent } from './assignee/assignee.component'; import { ConflictDialogComponent } from './conflict-dialog/conflict-dialog.component'; import { DescriptionComponent } from './description/description.component'; @@ -13,17 +12,18 @@ import { DuplicateOfComponent } from './duplicateOf/duplicate-of.component'; import { LabelComponent } from './label/label.component'; import { TitleComponent } from './title/title.component'; import { UnsureCheckboxComponent } from './unsure-checkbox/unsure-checkbox.component'; +import { TitleEditorComponent } from './title-editor/title-editor.component'; @NgModule({ imports: [ SharedModule, - TitleEditorModule, CommentEditorModule, MatProgressBarModule, NgxMatSelectSearchModule, MarkdownModule.forChild()], declarations: [ TitleComponent, + TitleEditorComponent, DescriptionComponent, LabelComponent, AssigneeComponent, @@ -34,6 +34,7 @@ import { UnsureCheckboxComponent } from './unsure-checkbox/unsure-checkbox.compo ], exports: [ TitleComponent, + TitleEditorComponent, DescriptionComponent, LabelComponent, AssigneeComponent, diff --git a/src/app/shared/title-editor/title-editor.component.css b/src/app/shared/issue/title-editor/title-editor.component.css similarity index 100% rename from src/app/shared/title-editor/title-editor.component.css rename to src/app/shared/issue/title-editor/title-editor.component.css diff --git a/src/app/shared/title-editor/title-editor.component.html b/src/app/shared/issue/title-editor/title-editor.component.html similarity index 96% rename from src/app/shared/title-editor/title-editor.component.html rename to src/app/shared/issue/title-editor/title-editor.component.html index a5fdf1575..72c4e397d 100644 --- a/src/app/shared/title-editor/title-editor.component.html +++ b/src/app/shared/issue/title-editor/title-editor.component.html @@ -1,7 +1,6 @@
{ let fixture: ComponentFixture; From ad3c2e391062ab9c16947dba0ae7bea1ba0b8006 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Fri, 23 Jun 2023 11:59:54 +0800 Subject: [PATCH 08/14] Center `title-button` text --- src/app/shared/issue/title/title.component.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/issue/title/title.component.css b/src/app/shared/issue/title/title.component.css index 44fc316c4..ffc5b2c93 100644 --- a/src/app/shared/issue/title/title.component.css +++ b/src/app/shared/issue/title/title.component.css @@ -13,6 +13,7 @@ .title-button { display: flex; flex-direction: row; + justify-content: center; align-items: center; margin: 5px; float: right; From 88a94ead116f5c481dbe741b84ef3be58e6083aa Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Fri, 23 Jun 2023 22:26:13 +0800 Subject: [PATCH 09/14] Fix lint errors --- src/app/shared/issue/issue-components.module.ts | 2 +- tests/app/shared/title-editor/title-editor.component.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/issue/issue-components.module.ts b/src/app/shared/issue/issue-components.module.ts index cecacac89..449165afe 100644 --- a/src/app/shared/issue/issue-components.module.ts +++ b/src/app/shared/issue/issue-components.module.ts @@ -10,9 +10,9 @@ import { DescriptionComponent } from './description/description.component'; import { DuplicatedIssuesComponent } from './duplicatedIssues/duplicated-issues.component'; import { DuplicateOfComponent } from './duplicateOf/duplicate-of.component'; import { LabelComponent } from './label/label.component'; +import { TitleEditorComponent } from './title-editor/title-editor.component'; import { TitleComponent } from './title/title.component'; import { UnsureCheckboxComponent } from './unsure-checkbox/unsure-checkbox.component'; -import { TitleEditorComponent } from './title-editor/title-editor.component'; @NgModule({ imports: [ diff --git a/tests/app/shared/title-editor/title-editor.component.spec.ts b/tests/app/shared/title-editor/title-editor.component.spec.ts index 175a205ea..3bca48c40 100644 --- a/tests/app/shared/title-editor/title-editor.component.spec.ts +++ b/tests/app/shared/title-editor/title-editor.component.spec.ts @@ -4,8 +4,8 @@ import { FormControl, FormGroup, FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { SharedModule } from '../../../../src/app/shared/shared.module'; import { TitleEditorComponent } from '../../../../src/app/shared/issue/title-editor/title-editor.component'; +import { SharedModule } from '../../../../src/app/shared/shared.module'; describe('CommentEditor', () => { let fixture: ComponentFixture; From cc4dc79c50f47713651ad0ffaec4ab840ee8012f Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Wed, 28 Jun 2023 14:40:33 +0800 Subject: [PATCH 10/14] Extract isUndo/isRedo methods into undoredo.model --- src/app/core/models/undoredo.model.ts | 15 +++++++++++++++ .../comment-editor.component.ts | 19 ++----------------- .../title-editor/title-editor.component.ts | 19 ++----------------- 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/app/core/models/undoredo.model.ts b/src/app/core/models/undoredo.model.ts index a59fd8d4f..80d98c906 100644 --- a/src/app/core/models/undoredo.model.ts +++ b/src/app/core/models/undoredo.model.ts @@ -20,6 +20,21 @@ export class UndoRedo { this.intervalTime = intervalTime; } + public static isUndo(event: KeyboardEvent) { + // prevents undo from firing when ctrl shift z is pressed + if (navigator.platform.indexOf('Mac') === 0) { + return event.metaKey && event.code === 'KeyZ' && !event.shiftKey; + } + return event.ctrlKey && event.code === 'KeyZ' && !event.shiftKey; + } + + public static isRedo(event: KeyboardEvent) { + if (navigator.platform.indexOf('Mac') === 0) { + return event.metaKey && event.shiftKey && event.code === 'KeyZ'; + } + return (event.ctrlKey && event.shiftKey && event.code === 'KeyZ') || (event.ctrlKey && event.code === 'KeyY'); + } + /** * Function to be called right before a change is made / stores the latest last state * preferably to be called with "beforeinput" event diff --git a/src/app/shared/comment-editor/comment-editor.component.ts b/src/app/shared/comment-editor/comment-editor.component.ts index 744957b62..9d5f27fa7 100644 --- a/src/app/shared/comment-editor/comment-editor.component.ts +++ b/src/app/shared/comment-editor/comment-editor.component.ts @@ -92,11 +92,11 @@ export class CommentEditorComponent implements OnInit { } onKeyPress(event: KeyboardEvent) { - if (this.isUndo(event)) { + if (UndoRedo.isUndo(event)) { event.preventDefault(); this.undo(); return; - } else if (this.isRedo(event)) { + } else if (UndoRedo.isRedo(event)) { this.redo(); event.preventDefault(); return; @@ -347,21 +347,6 @@ export class CommentEditorComponent implements OnInit { return event.ctrlKey; } - private isUndo(event: KeyboardEvent) { - // prevents undo from firing when ctrl shift z is pressed - if (navigator.platform.indexOf('Mac') === 0) { - return event.metaKey && event.code === 'KeyZ' && !event.shiftKey; - } - return event.ctrlKey && event.code === 'KeyZ' && !event.shiftKey; - } - - private isRedo(event: KeyboardEvent) { - if (navigator.platform.indexOf('Mac') === 0) { - return event.metaKey && event.shiftKey && event.code === 'KeyZ'; - } - return (event.ctrlKey && event.shiftKey && event.code === 'KeyZ') || (event.ctrlKey && event.code === 'KeyY'); - } - private insertOrRemoveCharsFromHighlightedText(char) { const selectionStart = this.commentTextArea.nativeElement.selectionStart; const selectionEnd = this.commentTextArea.nativeElement.selectionEnd; diff --git a/src/app/shared/issue/title-editor/title-editor.component.ts b/src/app/shared/issue/title-editor/title-editor.component.ts index b528772a0..4eb8724f2 100644 --- a/src/app/shared/issue/title-editor/title-editor.component.ts +++ b/src/app/shared/issue/title-editor/title-editor.component.ts @@ -56,11 +56,11 @@ export class TitleEditorComponent implements OnInit { } onKeyPress(event: KeyboardEvent) { - if (this.isUndo(event)) { + if (UndoRedo.isUndo(event)) { event.preventDefault(); this.undo(); return; - } else if (this.isRedo(event)) { + } else if (UndoRedo.isRedo(event)) { this.redo(); event.preventDefault(); return; @@ -116,19 +116,4 @@ export class TitleEditorComponent implements OnInit { this.titleInput.nativeElement.value = entry.text; this.titleInput.nativeElement.setSelectionRange(entry.selectStart, entry.selectEnd); } - - private isUndo(event: KeyboardEvent) { - // prevents undo from firing when ctrl shift z is pressed - if (navigator.platform.indexOf('Mac') === 0) { - return event.metaKey && event.code === 'KeyZ' && !event.shiftKey; - } - return event.ctrlKey && event.code === 'KeyZ' && !event.shiftKey; - } - - private isRedo(event: KeyboardEvent) { - if (navigator.platform.indexOf('Mac') === 0) { - return event.metaKey && event.shiftKey && event.code === 'KeyZ'; - } - return (event.ctrlKey && event.shiftKey && event.code === 'KeyZ') || (event.ctrlKey && event.code === 'KeyY'); - } } From 8efeafb92fa8c947891980af7a159f4685be3fd4 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Wed, 28 Jun 2023 14:43:55 +0800 Subject: [PATCH 11/14] Move title-editor.component.spec.ts --- .../{ => issue}/title-editor/title-editor.component.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/app/shared/{ => issue}/title-editor/title-editor.component.spec.ts (92%) diff --git a/tests/app/shared/title-editor/title-editor.component.spec.ts b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts similarity index 92% rename from tests/app/shared/title-editor/title-editor.component.spec.ts rename to tests/app/shared/issue/title-editor/title-editor.component.spec.ts index 3bca48c40..63b0a222e 100644 --- a/tests/app/shared/title-editor/title-editor.component.spec.ts +++ b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts @@ -4,8 +4,8 @@ import { FormControl, FormGroup, FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { TitleEditorComponent } from '../../../../src/app/shared/issue/title-editor/title-editor.component'; -import { SharedModule } from '../../../../src/app/shared/shared.module'; +import { TitleEditorComponent } from '../../../../../src/app/shared/issue/title-editor/title-editor.component'; +import { SharedModule } from '../../../../../src/app/shared/shared.module'; describe('CommentEditor', () => { let fixture: ComponentFixture; @@ -73,4 +73,4 @@ describe('CommentEditor', () => { }); }); }); -}); +}); \ No newline at end of file From b6f4b04b9d64aabe2290ea7ba73e5d789a2746a0 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Wed, 28 Jun 2023 14:45:08 +0800 Subject: [PATCH 12/14] Add additional tests for title-editor.component --- .../title-editor.component.spec.ts | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/tests/app/shared/issue/title-editor/title-editor.component.spec.ts b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts index 63b0a222e..36837e4bd 100644 --- a/tests/app/shared/issue/title-editor/title-editor.component.spec.ts +++ b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts @@ -7,12 +7,13 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { TitleEditorComponent } from '../../../../../src/app/shared/issue/title-editor/title-editor.component'; import { SharedModule } from '../../../../../src/app/shared/shared.module'; -describe('CommentEditor', () => { +describe('TitleEditor', () => { let fixture: ComponentFixture; let debugElement: DebugElement; let component: TitleEditorComponent; const TEST_INITIAL_TITLE = 'abc'; + const TEST_257_CHARS = '0'.repeat(257); beforeEach(() => { TestBed.configureTestingModule({ @@ -68,9 +69,74 @@ describe('CommentEditor', () => { textBox.value = '123'; textBox.dispatchEvent(new Event('input')); + await fixture.whenStable().then(() => { + expect(textBox.value).toEqual('123'); + }); + }); + + it('should undo a change on undo input', () => { + fixture.detectChanges(); + + const textBox: any = debugElement.query(By.css('input')).nativeElement; + + // saves empty state: { text: '', selectStart: 0, selectEnd: 0 } + component.history.forceSave(); + + textBox.value = '123'; + // saves state: { text: '123', selectStart: 3, selectEnd: 3 } + component.history.forceSave(); + + // undo to empty state: { text: '', selectStart: 0, selectEnd: 0 } + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', ctrlKey: true})); + + fixture.whenStable().then(() => { + expect(textBox.value).toEqual(''); + }); + }); + + it('should redo an undone change on redo input', () => { + fixture.detectChanges(); + + const textBox: any = debugElement.query(By.css('input')).nativeElement; + + // saves empty state: { text: '', selectStart: 0, selectEnd: 0 } + component.history.forceSave(); + + textBox.value = '123'; + // saves state: { text: '123', selectStart: 3, selectEnd: 3 } + component.history.forceSave(); + + // undo to empty state: { text: '', selectStart: 0, selectEnd: 0 } + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', ctrlKey: true})); + + // redo to state: { text: '123', selectStart: 3, selectEnd: 3 } + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'y', code: 'KeyY', ctrlKey: true})); + fixture.whenStable().then(() => { expect(textBox.value).toEqual('123'); }); }); + + it('blank input should be invalid', () => { + fixture.autoDetectChanges(); + + fixture.whenStable().then(() => { + expect(component.titleField.valid).toBe(false); + expect(component.titleField.hasError('required')).toEqual(true); + }); + }); + + it('input of more than 256 characters should be invalid', async () => { + fixture.detectChanges(); + + const textBox: any = debugElement.query(By.css('input')).nativeElement; + textBox.value = TEST_257_CHARS; + textBox.dispatchEvent(new Event('input')); + + await fixture.whenStable().then(() => { + expect(component.titleField.valid).toBe(false); + expect(component.titleField.hasError('maxlength')).toEqual(true); + }); + }); }); -}); \ No newline at end of file +}); From df664f91ed2a4b30d9d800c8c48068771cd4f9e9 Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Thu, 29 Jun 2023 12:08:33 +0800 Subject: [PATCH 13/14] Modify tests to account for macos --- .../title-editor.component.spec.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/app/shared/issue/title-editor/title-editor.component.spec.ts b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts index 36837e4bd..497ef5aa5 100644 --- a/tests/app/shared/issue/title-editor/title-editor.component.spec.ts +++ b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts @@ -87,7 +87,11 @@ describe('TitleEditor', () => { component.history.forceSave(); // undo to empty state: { text: '', selectStart: 0, selectEnd: 0 } - textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', ctrlKey: true})); + if (navigator.platform.indexOf('Mac') === 0) { + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', metaKey: true})); + } else { + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', ctrlKey: true})); + } fixture.whenStable().then(() => { expect(textBox.value).toEqual(''); @@ -107,10 +111,18 @@ describe('TitleEditor', () => { component.history.forceSave(); // undo to empty state: { text: '', selectStart: 0, selectEnd: 0 } - textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', ctrlKey: true})); + if (navigator.platform.indexOf('Mac') === 0) { + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', metaKey: true})); + } else { + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', ctrlKey: true})); + } // redo to state: { text: '123', selectStart: 3, selectEnd: 3 } - textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'y', code: 'KeyY', ctrlKey: true})); + if (navigator.platform.indexOf('Mac') === 0) { + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'y', code: 'KeyY', metaKey: true})); + } else { + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'y', code: 'KeyY', ctrlKey: true})); + } fixture.whenStable().then(() => { expect(textBox.value).toEqual('123'); From 269c52bed57fa089cfb52cc912450c9e671c744a Mon Sep 17 00:00:00 2001 From: chia-yh <0.chiayuhong.0@gmail.com> Date: Sat, 1 Jul 2023 00:07:25 +0800 Subject: [PATCH 14/14] Fix macos redo test --- .../issue/title-editor/title-editor.component.spec.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/app/shared/issue/title-editor/title-editor.component.spec.ts b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts index 497ef5aa5..6b9ce6f2e 100644 --- a/tests/app/shared/issue/title-editor/title-editor.component.spec.ts +++ b/tests/app/shared/issue/title-editor/title-editor.component.spec.ts @@ -111,16 +111,12 @@ describe('TitleEditor', () => { component.history.forceSave(); // undo to empty state: { text: '', selectStart: 0, selectEnd: 0 } + // redo to state: { text: '123', selectStart: 3, selectEnd: 3 } if (navigator.platform.indexOf('Mac') === 0) { textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', metaKey: true})); + textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', metaKey: true, shiftKey: true})); } else { textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'z', code: 'KeyZ', ctrlKey: true})); - } - - // redo to state: { text: '123', selectStart: 3, selectEnd: 3 } - if (navigator.platform.indexOf('Mac') === 0) { - textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'y', code: 'KeyY', metaKey: true})); - } else { textBox.dispatchEvent(new KeyboardEvent('keydown', {key: 'y', code: 'KeyY', ctrlKey: true})); }