Skip to content

Commit a60e56a

Browse files
authored
Merge branch '7.2.x' into SKrastev/fix-4209
2 parents 834a0fd + 3ba83f3 commit a60e56a

38 files changed

+501
-198
lines changed

projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ describe('IgxInput', () => {
3333
RequiredTwoWayDataBoundInputComponent,
3434
DataBoundDisabledInputComponent,
3535
ReactiveFormComponent,
36-
InputsWithSameNameAttributesComponent
36+
InputsWithSameNameAttributesComponent,
37+
ToggleRequiredWithNgModelInputComponent
3738
],
3839
imports: [
3940
IgxInputGroupModule,
@@ -247,6 +248,7 @@ describe('IgxInput', () => {
247248
});
248249

249250
it('When updating two inputs with same attribute names through ngModel, label should responds', fakeAsync(() => {
251+
250252
const fix = TestBed.createComponent(InputsWithSameNameAttributesComponent);
251253
fix.detectChanges();
252254

@@ -319,6 +321,116 @@ describe('IgxInput', () => {
319321
fix.detectChanges();
320322
expect(firstInputGroup.nativeElement.classList.contains('igx-input-group--invalid')).toBe(true);
321323
}));
324+
325+
it('Should style input when required is toggled dynamically.', () => {
326+
const fixture = TestBed.createComponent(ToggleRequiredWithNgModelInputComponent);
327+
fixture.detectChanges();
328+
329+
const instance = fixture.componentInstance;
330+
const input = instance.igxInputs.toArray()[1];
331+
const inputGroup = instance.igxInputGroups.toArray()[1];
332+
333+
expect(input.required).toBe(false);
334+
expect(inputGroup.isRequired).toBeFalsy();
335+
expect(input.valid).toBe(IgxInputState.INITIAL);
336+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(false);
337+
338+
dispatchInputEvent('focus', input.nativeElement, fixture);
339+
expect(input.valid).toBe(IgxInputState.INITIAL);
340+
341+
input.value = '123';
342+
dispatchInputEvent('input', input.nativeElement, fixture);
343+
expect(input.valid).toBe(IgxInputState.INITIAL);
344+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
345+
346+
dispatchInputEvent('blur', input.nativeElement, fixture);
347+
expect(input.valid).toBe(IgxInputState.INITIAL);
348+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
349+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
350+
351+
instance.isRequired = true;
352+
fixture.detectChanges();
353+
354+
expect(input.required).toBe(true);
355+
356+
expect(inputGroup.isRequired).toBeTruthy();
357+
expect(input.valid).toBe(IgxInputState.INITIAL);
358+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
359+
360+
dispatchInputEvent('focus', input.nativeElement, fixture);
361+
expect(input.valid).toBe(IgxInputState.INITIAL);
362+
363+
input.value = '';
364+
dispatchInputEvent('input', input.nativeElement, fixture);
365+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
366+
367+
dispatchInputEvent('blur', input.nativeElement, fixture);
368+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
369+
370+
input.value = '123';
371+
dispatchInputEvent('input', input.nativeElement, fixture);
372+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true);
373+
374+
dispatchInputEvent('blur', input.nativeElement, fixture);
375+
expect(input.valid).toBe(IgxInputState.INITIAL);
376+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
377+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
378+
});
379+
380+
it('Should style input with ngModel when required is toggled dynamically.', () => {
381+
const fixture = TestBed.createComponent(ToggleRequiredWithNgModelInputComponent);
382+
fixture.detectChanges();
383+
384+
const instance = fixture.componentInstance;
385+
const input = instance.igxInputs.toArray()[0];
386+
const inputGroup = instance.igxInputGroups.toArray()[0];
387+
388+
expect(input.required).toBe(false);
389+
expect(inputGroup.isRequired).toBeFalsy();
390+
expect(input.valid).toBe(IgxInputState.INITIAL);
391+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(false);
392+
393+
dispatchInputEvent('focus', input.nativeElement, fixture);
394+
expect(input.valid).toBe(IgxInputState.INITIAL);
395+
396+
input.value = '123';
397+
dispatchInputEvent('input', input.nativeElement, fixture);
398+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true);
399+
400+
dispatchInputEvent('blur', input.nativeElement, fixture);
401+
expect(input.valid).toBe(IgxInputState.INITIAL);
402+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
403+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
404+
405+
instance.isRequired = true;
406+
fixture.detectChanges();
407+
408+
expect(input.required).toBe(true);
409+
410+
expect(inputGroup.isRequired).toBeTruthy();
411+
expect(input.valid).toBe(IgxInputState.INITIAL);
412+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
413+
414+
dispatchInputEvent('focus', input.nativeElement, fixture);
415+
expect(input.valid).toBe(IgxInputState.INITIAL);
416+
417+
input.value = '';
418+
dispatchInputEvent('input', input.nativeElement, fixture);
419+
dispatchInputEvent('blur', input.nativeElement, fixture);
420+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
421+
422+
dispatchInputEvent('focus', input.nativeElement, fixture);
423+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
424+
425+
input.value = '123';
426+
dispatchInputEvent('input', input.nativeElement, fixture);
427+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true);
428+
429+
dispatchInputEvent('blur', input.nativeElement, fixture);
430+
expect(input.valid).toBe(IgxInputState.INITIAL);
431+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
432+
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
433+
});
322434
});
323435

324436
@Component({ template: `
@@ -529,6 +641,26 @@ class ReactiveFormComponent {
529641
}
530642
}
531643

644+
@Component({ template: `<igx-input-group>
645+
<label for="test" igxLabel>Test</label>
646+
<input name="test" type="text" igxInput [(ngModel)]="data" [required]="isRequired"/>
647+
</igx-input-group>
648+
<igx-input-group>
649+
<label for="test" igxLabel>Test</label>
650+
<input name="test" type="text" igxInput [value]="data1" [required]="isRequired"/>
651+
</igx-input-group>` })
652+
class ToggleRequiredWithNgModelInputComponent {
653+
@ViewChildren(IgxInputDirective)
654+
public igxInputs: QueryList<IgxInputDirective>;
655+
656+
@ViewChildren(IgxInputGroupComponent)
657+
public igxInputGroups: QueryList<IgxInputGroupComponent>;
658+
659+
public data = '';
660+
public data1 = '';
661+
public isRequired = false;
662+
}
663+
532664
function testRequiredValidation(inputElement, fixture) {
533665
dispatchInputEvent('focus', inputElement, fixture);
534666
inputElement.value = 'test';

projects/igniteui-angular/src/lib/directives/input/input.directive.ts

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export enum IgxInputState {
2424
}
2525

2626
@Directive({
27-
selector: '[igxInput]'
27+
selector: '[igxInput]',
28+
exportAs: 'igxInput'
2829
})
2930
export class IgxInputDirective implements AfterViewInit, OnDestroy {
3031
private _valid = IgxInputState.INITIAL;
@@ -92,6 +93,40 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
9293
public get disabled() {
9394
return this.nativeElement.hasAttribute('disabled');
9495
}
96+
97+
/**
98+
* Sets the `required` property.
99+
* ```html
100+
* <input-group>
101+
* <input igxInput #igxInput [required]="true">
102+
* </input-group>
103+
* ```
104+
* @memberof IgxInputDirective
105+
*/
106+
@Input()
107+
public set required(value: boolean) {
108+
if (typeof value === 'boolean') {
109+
this.nativeElement.required = this.inputGroup.isRequired = value;
110+
111+
if (value && !this.nativeElement.checkValidity()) {
112+
this._valid = IgxInputState.INVALID;
113+
} else {
114+
this._valid = IgxInputState.INITIAL;
115+
}
116+
}
117+
}
118+
119+
/**
120+
* Gets whether the igxInput is required.
121+
* ```typescript
122+
* let isRequired = this.igxInput.required;
123+
* ```
124+
* @memberof IgxInputDirective
125+
*/
126+
public get required() {
127+
return this.nativeElement.hasAttribute('required');
128+
}
129+
95130
/**
96131
* Sets/gets whether the `"igx-input-group__input"` class is added to the host element.
97132
* Default value is `false`.
@@ -227,16 +262,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
227262
}
228263
}
229264
}
230-
/**
231-
* Gets whether the igxInput is required.
232-
* ```typescript
233-
* let isRequired = this.igxInput.required;
234-
* ```
235-
* @memberof IgxInputDirective
236-
*/
237-
public get required() {
238-
return this.nativeElement.hasAttribute('required');
239-
}
240265
/**
241266
* Gets whether the igxInput has a placeholder.
242267
* ```typescript
@@ -287,6 +312,18 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
287312
public get valid(): IgxInputState {
288313
return this._valid;
289314
}
315+
316+
/**
317+
* Gets whether the igxInput is valid.
318+
* ```typescript
319+
* let valid = this.igxInput.isValid;
320+
* ```
321+
* @memberof IgxInputDirective
322+
*/
323+
public get isValid(): boolean {
324+
return this.valid !== IgxInputState.INVALID;
325+
}
326+
290327
/**
291328
* Sets the state of the igxInput.
292329
* ```typescript

projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,36 @@ describe('IgxToggle', () => {
420420
expect(toggle.onClosed.emit).toHaveBeenCalledTimes(1);
421421
}));
422422

423+
it('fix for #4222 - Should emit closed when closed second time', fakeAsync(() => {
424+
const fixture = TestBed.createComponent(IgxToggleTestComponent);
425+
fixture.detectChanges();
426+
427+
const toggle = fixture.componentInstance.toggle;
428+
toggle.onClosed.subscribe(() => {
429+
toggle.open();
430+
});
431+
432+
spyOn(toggle.onOpening, 'emit');
433+
spyOn(toggle.onClosed, 'emit').and.callThrough();
434+
435+
toggle.open();
436+
tick();
437+
fixture.detectChanges();
438+
expect(toggle.onOpening.emit).toHaveBeenCalledTimes(1);
439+
440+
toggle.close();
441+
tick();
442+
fixture.detectChanges();
443+
expect(toggle.onClosed.emit).toHaveBeenCalledTimes(1);
444+
expect(toggle.onOpening.emit).toHaveBeenCalledTimes(2);
445+
446+
toggle.close();
447+
tick();
448+
fixture.detectChanges();
449+
expect(toggle.onClosed.emit).toHaveBeenCalledTimes(2);
450+
expect(toggle.onOpening.emit).toHaveBeenCalledTimes(3);
451+
}));
452+
423453
describe('overlay settings', () => {
424454
configureTestSuite();
425455
it('should pass correct defaults from IgxToggleActionDirective and respect outsideClickClose', fakeAsync(() => {

projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,8 @@ export class IgxToggleDirective implements IToggleView, OnInit, OnDestroy {
305305
this._collapsed = true;
306306
this.cdr.detectChanges();
307307
delete this._overlayId;
308-
this.onClosed.emit();
309308
this.unsubscribe();
309+
this.onClosed.emit();
310310
}
311311

312312
private unsubscribe() {

projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-custom-dialog.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export class IgxExcelStyleCustomDialogComponent implements AfterViewInit {
148148
this.expressionsList[this.expressionsList.length - 1].afterOperator = null;
149149
}
150150

151-
this.filteringService.filter(this.column.field, this.expressionsList);
151+
this.filteringService.filterInternal(this.column.field, this.expressionsList);
152152
this.closeDialog();
153153
}
154154

projects/igniteui-angular/src/lib/grids/filtering/excel-style/grid.excel-style-filtering.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,11 @@ export class IgxGridExcelStyleFilteringComponent implements OnDestroy, AfterView
180180
this.customDialog.expressionsList = this.expressionsList;
181181
this.populateColumnData();
182182

183-
const se = this.grid.sortingExpressions.find(expr => expr.fieldName === this.column.field);
184-
if (se) {
185-
this.excelStyleSorting.selectButton(se.dir);
183+
if (this.excelStyleSorting) {
184+
const se = this.grid.sortingExpressions.find(expr => expr.fieldName === this.column.field);
185+
if (se) {
186+
this.excelStyleSorting.selectButton(se.dir);
187+
}
186188
}
187189

188190
requestAnimationFrame(() => {
@@ -528,7 +530,7 @@ export class IgxGridExcelStyleFilteringComponent implements OnDestroy, AfterView
528530
});
529531
});
530532
this.expressionsList = new Array<ExpressionUI>();
531-
this.filteringService.filter(this.column.field, filterTree);
533+
this.filteringService.filterInternal(this.column.field, filterTree);
532534
} else {
533535
this.filteringService.clearFilter(this.column.field);
534536
}

projects/igniteui-angular/src/lib/grids/filtering/grid-filtering-cell.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit, DoC
253253
this.filteringService.removeExpression(this.column.field, indexToRemove);
254254

255255
this.updateVisibleFilters();
256-
this.filteringService.filter(this.column.field);
256+
this.filteringService.filterInternal(this.column.field);
257257
}
258258

259259
private isMoreIconHidden(): boolean {

projects/igniteui-angular/src/lib/grids/filtering/grid-filtering-row.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,6 @@ export class IgxGridFilteringRowComponent implements AfterViewInit {
708708
}
709709

710710
private filter() {
711-
this.filteringService.filter(this.column.field);
711+
this.filteringService.filterInternal(this.column.field);
712712
}
713713
}

0 commit comments

Comments
 (0)