Skip to content

Commit c3fe5f7

Browse files
alinelariguetjhosefmarks
authored andcommitted
fix(rich-text): corrige atualização do model via código
Corrige comportamento ao atualizar o valor do componente via código. Fixes DTHFUI-3787
1 parent c675aea commit c3fe5f7

File tree

7 files changed

+139
-15
lines changed

7 files changed

+139
-15
lines changed

projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-base.component.spec.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import * as ValidatorsFunctions from '../validators';
55
import { expectPropertiesValues } from '../../../util-test/util-expect.spec';
66

77
import { PoRichTextBaseComponent } from './po-rich-text-base.component';
8+
import { PoRichTextService } from './po-rich-text.service';
89

910
@Directive()
1011
class PoRichTextComponent extends PoRichTextBaseComponent {}
1112

1213
describe('PoRichTextBaseComponent:', () => {
14+
const poRichTextService: PoRichTextService = new PoRichTextService();
1315
let component: PoRichTextComponent;
1416

1517
beforeEach(() => {
16-
component = new PoRichTextComponent();
18+
component = new PoRichTextComponent(poRichTextService);
1719
});
1820

1921
it('should be created', () => {
@@ -103,11 +105,14 @@ describe('PoRichTextBaseComponent:', () => {
103105
});
104106

105107
it('writeValue: should set value with the received param', () => {
108+
spyOn(component['richTextService'], 'emitModel');
109+
const model = 'value B';
106110
component.value = 'value A';
107111

108-
component.writeValue('value B');
112+
component.writeValue(model);
109113

110-
expect(component.value).toBe('value B');
114+
expect(component.value).toBe(model);
115+
expect(component['richTextService'].emitModel).toHaveBeenCalledWith(model);
111116
});
112117

113118
it('updateModel: should call onChangeModel method if onChangeModel isn`t false', () => {

projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-base.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EventEmitter, Input, Output, Directive } from '@angular/core';
44
import { convertToBoolean } from '../../../utils/util';
55
import { requiredFailed } from '../validators';
66
import { InputBoolean } from '../../../decorators';
7+
import { PoRichTextService } from './po-rich-text.service';
78

89
/**
910
* @description
@@ -161,6 +162,8 @@ export abstract class PoRichTextBaseComponent implements ControlValueAccessor, V
161162
/** Evento disparado ao modificar valor do model e que recebe como parâmetro o valor alterado. */
162163
@Output('p-change-model') changeModel?: EventEmitter<any> = new EventEmitter();
163164

165+
constructor(private richTextService: PoRichTextService) {}
166+
164167
// Função implementada do ControlValueAccessor
165168
// Usada para interceptar as mudanças e não atualizar automaticamente o Model
166169
registerOnChange(func: any): void {
@@ -189,6 +192,8 @@ export abstract class PoRichTextBaseComponent implements ControlValueAccessor, V
189192

190193
writeValue(value: string): void {
191194
this.value = value;
195+
196+
this.richTextService.emitModel(value);
192197
}
193198

194199
// Executa a função onChange

projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-body/po-rich-text-body.component.spec.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
22

3+
import { of } from 'rxjs';
4+
35
import * as UtilsFunction from '../../../../utils/util';
46
import { configureTestSuite } from './../../../../util-test/util-expect.spec';
57

@@ -28,26 +30,55 @@ describe('PoRichTextBodyComponent:', () => {
2830
});
2931

3032
describe('Methods:', () => {
33+
const fakeSubscription = <any>{ unsubscribe: () => {} };
34+
3135
it('onInit: should update `bodyElement`', () => {
3236
const expectedValue = 'on';
3337
component.ngOnInit();
3438

3539
expect(component.bodyElement.nativeElement.designMode).toEqual(expectedValue);
3640
});
3741

38-
it('onInit: should call `updateValueWithModelValue` and `addClickListenerOnAnchorElements`', fakeAsync(() => {
42+
it('onInit: should call `richTextService.getModel.subscribe`, update the model and add listeners to the anchor elements', fakeAsync(() => {
43+
const response = 'valor inicial';
44+
spyOn(component['richTextService'], 'getModel').and.returnValue(of(response));
3945
spyOn(component, <any>'updateValueWithModelValue');
4046
spyOn(component, <any>'addClickListenerOnAnchorElements');
4147

4248
component.ngOnInit();
43-
tick(50);
4449

45-
expect(component['updateValueWithModelValue']).toHaveBeenCalledBefore(
46-
component['addClickListenerOnAnchorElements']
47-
);
48-
expect(component['addClickListenerOnAnchorElements']).toHaveBeenCalled();
50+
component['richTextService'].getModel().subscribe(() => {
51+
expect(component['updateValueWithModelValue']).toHaveBeenCalled();
52+
expect(component['addClickListenerOnAnchorElements']).toHaveBeenCalled();
53+
});
54+
55+
tick();
56+
57+
expect(component['modelSubscription']).toBeTruthy();
58+
expect(component.modelValue).toEqual(response);
4959
}));
5060

61+
it('ngOnDestroy: should unsubscribe modelSubscription.', () => {
62+
component['modelSubscription'] = fakeSubscription;
63+
64+
spyOn(fakeSubscription, <any>'unsubscribe');
65+
66+
component.ngOnDestroy();
67+
68+
expect(fakeSubscription.unsubscribe).toHaveBeenCalled();
69+
});
70+
71+
it('ngOnDestroy: should not unsubscribe if modelSubscription is falsy.', () => {
72+
component['modelSubscription'] = fakeSubscription;
73+
74+
spyOn(fakeSubscription, <any>'unsubscribe');
75+
76+
component['modelSubscription'] = undefined;
77+
component.ngOnDestroy();
78+
79+
expect(fakeSubscription.unsubscribe).not.toHaveBeenCalled();
80+
});
81+
5182
describe('executeCommand:', () => {
5283
it('should call `focus`', () => {
5384
const spyFocus = spyOn(component.bodyElement.nativeElement, <any>'focus');
@@ -1039,6 +1070,15 @@ describe('PoRichTextBodyComponent:', () => {
10391070
);
10401071
});
10411072

1073+
it('updateValueWithModelValue: shouldn`t call `bodyElement.nativeElement.insertAdjacentHTML`', () => {
1074+
component.modelValue = undefined;
1075+
1076+
spyOn(component.bodyElement.nativeElement, 'insertAdjacentHTML');
1077+
component['updateValueWithModelValue']();
1078+
1079+
expect(component.bodyElement.nativeElement.insertAdjacentHTML).not.toHaveBeenCalled();
1080+
});
1081+
10421082
it('verifyCursorPositionInFirefoxIEEdge: should return true if nodeName is an A tag', () => {
10431083
const element = { nodeName: 'A' };
10441084
const textSelection = {

projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-body/po-rich-text-body.component.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
1+
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild, OnDestroy } from '@angular/core';
2+
3+
import { Subscription } from 'rxjs';
24

35
import { isFirefox, isIE, isIEOrEdge, openExternalLink } from './../../../../utils/util';
46
import { PoKeyCodeEnum } from './../../../../enums/po-key-code.enum';
7+
import { PoRichTextService } from '../po-rich-text.service';
58

69
const poRichTextBodyCommands = [
710
'bold',
@@ -19,11 +22,12 @@ const poRichTextBodyCommands = [
1922
selector: 'po-rich-text-body',
2023
templateUrl: './po-rich-text-body.component.html'
2124
})
22-
export class PoRichTextBodyComponent implements OnInit {
25+
export class PoRichTextBodyComponent implements OnInit, OnDestroy {
2326
private isLinkEditing: boolean;
2427
private linkElement: any;
2528
private timeoutChange: any;
2629
private valueBeforeChange: any;
30+
private modelSubscription: Subscription;
2731

2832
@ViewChild('bodyElement', { static: true }) bodyElement: ElementRef;
2933

@@ -45,16 +49,23 @@ export class PoRichTextBodyComponent implements OnInit {
4549

4650
@Output('p-value') value = new EventEmitter<any>();
4751

52+
constructor(private richTextService: PoRichTextService) {}
53+
4854
ngOnInit() {
4955
this.bodyElement.nativeElement.designMode = 'on';
5056

51-
// timeout necessário para setar o valor vindo do writeValue do componente principal.
52-
setTimeout(() => {
57+
this.modelSubscription = this.richTextService.getModel().subscribe(modelValue => {
58+
this.modelValue = modelValue;
59+
this.bodyElement.nativeElement.innerHTML = '';
5360
this.updateValueWithModelValue();
5461
this.addClickListenerOnAnchorElements();
5562
});
5663
}
5764

65+
ngOnDestroy() {
66+
this.modelSubscription?.unsubscribe();
67+
}
68+
5869
executeCommand(command: string | { command: any; value: string | any }) {
5970
this.bodyElement.nativeElement.focus();
6071

projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
44

55
import { PoRichTextBaseComponent } from './po-rich-text-base.component';
66
import { PoRichTextBodyComponent } from './po-rich-text-body/po-rich-text-body.component';
7+
import { PoRichTextService } from './po-rich-text.service';
78

89
/* istanbul ignore next */
910
const providers = [
@@ -57,8 +58,8 @@ export class PoRichTextComponent extends PoRichTextBaseComponent implements Afte
5758
return this.errorMessage !== '' && !this.value && this.required && this.invalid ? this.errorMessage : '';
5859
}
5960

60-
constructor(private element: ElementRef) {
61-
super();
61+
constructor(private element: ElementRef, richTextService: PoRichTextService) {
62+
super(richTextService);
6263
}
6364

6465
ngAfterViewInit() {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { TestBed, async, inject } from '@angular/core/testing';
2+
3+
import { Observable } from 'rxjs';
4+
5+
import { PoRichTextService } from './po-rich-text.service';
6+
7+
describe('Service: PoRichText', () => {
8+
let richTextService: PoRichTextService;
9+
10+
beforeEach(() => {
11+
TestBed.configureTestingModule({
12+
providers: [PoRichTextService]
13+
});
14+
15+
richTextService = TestBed.inject(PoRichTextService);
16+
});
17+
18+
it('should ...', inject([PoRichTextService], (service: PoRichTextService) => {
19+
expect(service).toBeTruthy();
20+
}));
21+
22+
describe('Methods:', () => {
23+
it('emitModel: should call model.next with value', () => {
24+
const value = 'test';
25+
spyOn(richTextService['model'], 'next');
26+
richTextService.emitModel('test');
27+
28+
expect(richTextService['model'].next).toHaveBeenCalledWith(value);
29+
});
30+
31+
it('getModel: should call model.asObservable', () => {
32+
spyOn(richTextService['model'], 'asObservable');
33+
richTextService.getModel();
34+
35+
expect(richTextService['model'].asObservable).toHaveBeenCalled();
36+
});
37+
38+
it('getModel: should return an instanceof Observable', () => {
39+
const result = richTextService.getModel();
40+
41+
expect(result instanceof Observable).toBeTruthy();
42+
});
43+
});
44+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Injectable } from '@angular/core';
2+
3+
import { Subject } from 'rxjs';
4+
5+
@Injectable({
6+
providedIn: 'root'
7+
})
8+
export class PoRichTextService {
9+
private model = new Subject<string>();
10+
11+
emitModel(value: string) {
12+
this.model.next(value);
13+
}
14+
15+
getModel() {
16+
return this.model.asObservable();
17+
}
18+
}

0 commit comments

Comments
 (0)