From a6ff5b04338433bd0c6590ca1e1aa6635d81f915 Mon Sep 17 00:00:00 2001 From: Mohamed Ben Makhlouf Date: Mon, 14 Oct 2024 19:22:13 +0200 Subject: [PATCH 01/10] move to new control flow syntax --- src/app/components/inplace/inplace.ts | 83 ++++++++++++++------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/app/components/inplace/inplace.ts b/src/app/components/inplace/inplace.ts index b2147af4d00..4aee4390af2 100755 --- a/src/app/components/inplace/inplace.ts +++ b/src/app/components/inplace/inplace.ts @@ -2,7 +2,6 @@ import { CommonModule } from '@angular/common'; import { AfterContentInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, ContentChildren, EventEmitter, @@ -45,44 +44,50 @@ export class InplaceContent {} [class]="styleClass" [attr.aria-live]="'polite'" > -
- - -
-
- - - - - - - -
+ @if (!active) { +
+ + +
+ } @else { +
+ + + @if (closable) { + @if (closeIcon) { + + } @else { + + } + } +
+ } `, changeDetection: ChangeDetectionStrategy.OnPush, From 52057ab4ed7f8f773253c16bde2b271b73347a44 Mon Sep 17 00:00:00 2001 From: Mohamed Ben Makhlouf Date: Mon, 14 Oct 2024 19:33:13 +0200 Subject: [PATCH 02/10] move to input signals --- src/app/components/inplace/inplace.spec.ts | 27 +++++++----- src/app/components/inplace/inplace.ts | 51 +++++++++++----------- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/app/components/inplace/inplace.spec.ts b/src/app/components/inplace/inplace.spec.ts index 844eec362f0..400799a2092 100755 --- a/src/app/components/inplace/inplace.spec.ts +++ b/src/app/components/inplace/inplace.spec.ts @@ -1,3 +1,4 @@ +import { ComponentRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -8,6 +9,7 @@ import { Inplace } from './inplace'; describe('Inplace', () => { let inplace: Inplace; let fixture: ComponentFixture; + let inplaceRef: ComponentRef; beforeEach(() => { TestBed.configureTestingModule({ @@ -17,6 +19,7 @@ describe('Inplace', () => { fixture = TestBed.createComponent(Inplace); inplace = fixture.componentInstance; + inplaceRef = fixture.componentRef; }); it('should display by default', () => { @@ -27,10 +30,10 @@ describe('Inplace', () => { }); it('should change style styleClass and closable', () => { - inplace.style = { height: '300px' }; - inplace.styleClass = 'Primeng ROCKS!'; - inplace.closable = true; - inplace.active = true; + inplaceRef.setInput('style', { height: '300px' }); + inplaceRef.setInput('styleClass', 'Primeng ROCKS!'); + inplaceRef.setInput('closable', true); + inplaceRef.setInput('active', true); fixture.detectChanges(); const inplaceEl = fixture.debugElement.query(By.css('div')); @@ -42,7 +45,7 @@ describe('Inplace', () => { }); it('should call activate and deactivate', () => { - inplace.closable = true; + inplaceRef.setInput('closable', true); fixture.detectChanges(); const activateSpy = spyOn(inplace, 'activate').and.callThrough(); @@ -51,19 +54,19 @@ describe('Inplace', () => { displayEl.nativeElement.click(); fixture.detectChanges(); - expect(inplace.active).toEqual(true); + expect(inplace.active()).toEqual(true); expect(activateSpy).toHaveBeenCalled(); const closableButtonEl = fixture.debugElement.query(By.css('button')); closableButtonEl.nativeElement.click(); fixture.detectChanges(); - expect(inplace.active).toEqual(false); + expect(inplace.active()).toEqual(false); expect(deactivateSpy).toHaveBeenCalled(); }); it('should disabled', () => { - inplace.closable = true; - inplace.disabled = true; + inplaceRef.setInput('closable', true); + inplaceRef.setInput('disabled', true); fixture.detectChanges(); const activateSpy = spyOn(inplace, 'activate').and.callThrough(); @@ -72,9 +75,9 @@ describe('Inplace', () => { displayEl.nativeElement.click(); fixture.detectChanges(); - expect(inplace.active).toEqual(false); + expect(inplace.active()).toEqual(false); expect(activateSpy).toHaveBeenCalled(); - inplace.active = true; + inplace.active.set(true); fixture.detectChanges(); inplace.cd.detectChanges(); @@ -82,7 +85,7 @@ describe('Inplace', () => { closableButtonEl.nativeElement.click(); fixture.detectChanges(); - expect(inplace.active).toEqual(true); + expect(inplace.active()).toEqual(true); expect(deactivateSpy).toHaveBeenCalled(); }); }); diff --git a/src/app/components/inplace/inplace.ts b/src/app/components/inplace/inplace.ts index 4aee4390af2..8801ba10045 100755 --- a/src/app/components/inplace/inplace.ts +++ b/src/app/components/inplace/inplace.ts @@ -5,7 +5,7 @@ import { Component, ContentChildren, EventEmitter, - Input, + input, NgModule, Output, QueryList, @@ -13,6 +13,7 @@ import { ViewEncapsulation, booleanAttribute, inject, + model, } from '@angular/core'; import { PrimeTemplate, SharedModule } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; @@ -39,19 +40,19 @@ export class InplaceContent {} selector: 'p-inplace', template: `
- @if (!active) { + @if (!active()) {
@@ -62,14 +63,14 @@ export class InplaceContent {} - @if (closable) { - @if (closeIcon) { + @if (closable()) { + @if (closeIcon()) { } @else {
} @else {
@if (closable()) { @if (closeIcon()) { @@ -80,10 +80,10 @@ export class InplaceContent {} (click)="onDeactivateClick($event)" [attr.aria-label]="closeAriaLabel()" > - @if (!closeIconTemplate) { + @if (!closeIconTemplate()) { } - + } } @@ -96,7 +96,7 @@ export class InplaceContent {} providers: [InplaceStyle], }) -export class Inplace extends BaseComponent implements AfterContentInit { +export class Inplace extends BaseComponent { /** * Whether the content is displayed or not. * @group Props @@ -150,33 +150,22 @@ export class Inplace extends BaseComponent implements AfterContentInit { */ onDeactivate: OutputEmitterRef = output(); - @ContentChildren(PrimeTemplate) templates: QueryList | undefined; + templates: Signal = contentChildren(PrimeTemplate); - displayTemplate: TemplateRef | undefined; + private mappedTemplates = computed<{ [key: string]: TemplateRef }>(() => { + return (this.templates() || []).reduce((prev, item) => { + prev[item.getType()] = item.template; + return prev; + }, {}); + }); - contentTemplate: TemplateRef | undefined; + displayTemplate = computed | undefined>(() => this.mappedTemplates()['display']); - closeIconTemplate: TemplateRef | undefined; + contentTemplate = computed | undefined>(() => this.mappedTemplates()['content']); - _componentStyle = inject(InplaceStyle); - - ngAfterContentInit() { - this.templates?.forEach((item) => { - switch (item.getType()) { - case 'display': - this.displayTemplate = item.template; - break; - - case 'closeicon': - this.closeIconTemplate = item.template; - break; + closeIconTemplate = computed | undefined>(() => this.mappedTemplates()['closeicon']); - case 'content': - this.contentTemplate = item.template; - break; - } - }); - } + _componentStyle = inject(InplaceStyle); onActivateClick(event: MouseEvent) { if (!this.preventClick()) this.activate(event); From 70ac6a5e59dcd88f2c83ea79d7ec5527123f1a84 Mon Sep 17 00:00:00 2001 From: Mohamed Ben Makhlouf Date: Mon, 14 Oct 2024 19:54:33 +0200 Subject: [PATCH 07/10] improve imports --- src/app/components/inplace/inplace.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/inplace/inplace.ts b/src/app/components/inplace/inplace.ts index 386b9575330..4416552943e 100755 --- a/src/app/components/inplace/inplace.ts +++ b/src/app/components/inplace/inplace.ts @@ -1,4 +1,4 @@ -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle, NgTemplateOutlet } from '@angular/common'; import { computed, ChangeDetectionStrategy, @@ -206,7 +206,7 @@ export class Inplace extends BaseComponent { } @NgModule({ - imports: [CommonModule, ButtonModule, SharedModule, TimesIcon], + imports: [NgClass, NgStyle, NgTemplateOutlet, ButtonModule, SharedModule, TimesIcon], exports: [Inplace, InplaceDisplay, InplaceContent, ButtonModule, SharedModule], declarations: [Inplace, InplaceDisplay, InplaceContent], }) From 99ea7b17159b8c568d45c0057ef715537916d7eb Mon Sep 17 00:00:00 2001 From: Mohamed Ben Makhlouf Date: Mon, 14 Oct 2024 20:24:51 +0200 Subject: [PATCH 08/10] enforce testing --- src/app/components/inplace/inplace.spec.ts | 109 ++++++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/src/app/components/inplace/inplace.spec.ts b/src/app/components/inplace/inplace.spec.ts index 400799a2092..20ae621e295 100755 --- a/src/app/components/inplace/inplace.spec.ts +++ b/src/app/components/inplace/inplace.spec.ts @@ -1,23 +1,42 @@ -import { ComponentRef } from '@angular/core'; +import { Component, ComponentRef, TemplateRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { TimesIcon } from 'primeng/icons/times'; import { ButtonModule } from '../button/button'; -import { Inplace } from './inplace'; +import { Inplace, InplaceModule } from './inplace'; + +@Component({ + template: ` + + + + + Display + Content + + `, + standalone: true, + imports: [InplaceModule], +}) +class TestHostComponent { + closable = false; + active = false; +} describe('Inplace', () => { let inplace: Inplace; let fixture: ComponentFixture; + let fixtureTest: ComponentFixture; let inplaceRef: ComponentRef; beforeEach(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, ButtonModule, TimesIcon], - declarations: [Inplace], + imports: [NoopAnimationsModule, ButtonModule, TimesIcon, InplaceModule, TestHostComponent], }); fixture = TestBed.createComponent(Inplace); + fixtureTest = TestBed.createComponent(TestHostComponent); inplace = fixture.componentInstance; inplaceRef = fixture.componentRef; }); @@ -88,4 +107,86 @@ describe('Inplace', () => { expect(inplace.active()).toEqual(true); expect(deactivateSpy).toHaveBeenCalled(); }); + + it('should activate content on Enter key press', () => { + fixture.detectChanges(); + + const displayElement = fixture.debugElement.query(By.css('.p-inplace-display')); + displayElement.triggerEventHandler('keydown', { code: 'Enter', preventDefault: () => {} }); + + fixture.detectChanges(); + + expect(inplace.active()).toBeTrue(); + }); + + it('should prevent activation when preventClick is true', () => { + inplaceRef.setInput('preventClick', true); + fixture.detectChanges(); + + const displayElement = fixture.debugElement.query(By.css('.p-inplace-display')); + displayElement.triggerEventHandler('click', {}); + + fixture.detectChanges(); + + expect(inplace.active()).toBeFalse(); + }); + + it('should render custom close button icon when provided', () => { + inplaceRef.setInput('closable', true); + inplaceRef.setInput('closeIcon', 'custom-icon'); + inplace.active.set(true); + fixture.detectChanges(); + + const buttonIcon = fixture.debugElement.query(By.css('span.custom-icon')); + expect(buttonIcon).toBeTruthy(); + }); + + it('should set aria-label for the close button', () => { + inplaceRef.setInput('closable', true); + inplaceRef.setInput('closeAriaLabel', 'Close'); + inplace.active.set(true); + fixture.detectChanges(); + + const closeButton = fixture.debugElement.query(By.css('button')); + expect(closeButton.attributes['aria-label']).toBe('Close'); + }); + + it('should render the default close icon if no closeIconTemplate is provided', () => { + inplaceRef.setInput('closable', true); + inplace.active.set(true); // Ensure content mode is active + fixture.detectChanges(); + + const closeButton = fixture.debugElement.query(By.css('.p-button-icon-only')); + const timesIcon = fixture.debugElement.query(By.css('TimesIcon')); // Default icon + expect(closeButton).toBeTruthy(); // Close button should be present + expect(timesIcon).toBeTruthy(); // Ensure the default icon is rendered + }); + + it('should render the content template when active', () => { + fixtureTest.componentInstance.active = true; // Set the component to active mode + fixtureTest.detectChanges(); + + const contentElement = fixtureTest.debugElement.query(By.css('.p-inplace-content')); + expect(contentElement).toBeTruthy(); // Content should be visible + expect(contentElement.nativeElement.textContent).toBe('Content'); // Ensure content template is rendered + }); + + it('should render the custom close icon template when closable', () => { + fixtureTest.componentInstance.closable = true; // // Enable close button + fixtureTest.componentInstance.active = true; // Ensure it's in content mode + fixtureTest.detectChanges(); + + const closeButton = fixtureTest.debugElement.query(By.css('button')); + const closeIcon = closeButton.query(By.css('span.my-icon')); + expect(closeButton).toBeTruthy(); // Close button should be present + expect(closeIcon).toBeTruthy(); // custom close icon button should be present + }); + + it('should render the display template when inactive', () => { + fixtureTest.detectChanges(); + + const displayElement = fixtureTest.debugElement.query(By.css('.p-inplace-display')); + expect(displayElement).toBeTruthy(); // Display element should be present + expect(displayElement.nativeElement.textContent).toBe('Display'); // Ensure the template is rendered + }); }); From eba1da6e2896088ec851c127e13295a589fe01c7 Mon Sep 17 00:00:00 2001 From: Mohamed Ben Makhlouf Date: Sun, 20 Oct 2024 19:17:43 +0200 Subject: [PATCH 09/10] handle conflict --- src/app/components/inplace/inplace.spec.ts | 14 ++++----- src/app/components/inplace/inplace.ts | 35 ++++++++-------------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/app/components/inplace/inplace.spec.ts b/src/app/components/inplace/inplace.spec.ts index 20ae621e295..ead29050ce3 100755 --- a/src/app/components/inplace/inplace.spec.ts +++ b/src/app/components/inplace/inplace.spec.ts @@ -2,22 +2,20 @@ import { Component, ComponentRef, TemplateRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { TimesIcon } from 'primeng/icons/times'; -import { ButtonModule } from '../button/button'; -import { Inplace, InplaceModule } from './inplace'; +import { Inplace } from './inplace'; @Component({ template: ` - + - Display - Content + Display + Content `, standalone: true, - imports: [InplaceModule], + imports: [Inplace], }) class TestHostComponent { closable = false; @@ -32,7 +30,7 @@ describe('Inplace', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, ButtonModule, TimesIcon, InplaceModule, TestHostComponent], + imports: [NoopAnimationsModule, Inplace, TestHostComponent], }); fixture = TestBed.createComponent(Inplace); diff --git a/src/app/components/inplace/inplace.ts b/src/app/components/inplace/inplace.ts index 7cbe57f81ed..4875565b6ee 100755 --- a/src/app/components/inplace/inplace.ts +++ b/src/app/components/inplace/inplace.ts @@ -3,23 +3,20 @@ import { booleanAttribute, ChangeDetectionStrategy, Component, - ContentChild, - contentChildren, + contentChild, OutputEmitterRef, inject, input, NgModule, output, - Signal, TemplateRef, ViewEncapsulation, - computed, - model + model, } from '@angular/core'; -import { PrimeTemplate } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { TimesIcon } from 'primeng/icons/times'; import { BaseComponent } from 'primeng/basecomponent'; +import { Ripple } from 'primeng//ripple'; import { InplaceStyle } from './style/inplacestyle'; @Component({ @@ -42,7 +39,7 @@ export class InplaceContent {} @Component({ selector: 'p-inplace', standalone: true, - imports: [NgClass, NgStyle, NgTemplateOutlet, ButtonModule, TimesIcon], + imports: [NgClass, NgStyle, NgTemplateOutlet, ButtonModule, TimesIcon, Ripple], template: `
} @else {