diff --git a/packages/primeng/src/toggleswitch/toggleswitch.spec.ts b/packages/primeng/src/toggleswitch/toggleswitch.spec.ts index e6c599dcd52..83341c9daeb 100755 --- a/packages/primeng/src/toggleswitch/toggleswitch.spec.ts +++ b/packages/primeng/src/toggleswitch/toggleswitch.spec.ts @@ -1,171 +1,113 @@ -import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentRef } from '@angular/core'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { InputSwitch } from './inputswitch'; +import { ToggleSwitch } from './toggleswitch'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -describe('InputSwitch', () => { - let inputswitch: InputSwitch; - let fixture: ComponentFixture; +describe('ToggleSwitch', () => { + let toggleSwitch: ToggleSwitch; + let fixture: ComponentFixture; + let toggleSwitchRef: ComponentRef; beforeEach(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule], - declarations: [InputSwitch] + imports: [NoopAnimationsModule, ToggleSwitch] }); - fixture = TestBed.createComponent(InputSwitch); - inputswitch = fixture.componentInstance; - }); - - it('should created by default', () => { + fixture = TestBed.createComponent(ToggleSwitch); + toggleSwitch = fixture.componentInstance; + toggleSwitchRef = fixture.componentRef; fixture.detectChanges(); - - const inputSwitchEl = fixture.debugElement.query(By.css('div')).nativeElement; - expect(inputSwitchEl).toBeTruthy(); }); - it('should disabled', () => { - inputswitch.disabled = true; - fixture.detectChanges(); - - const onClickSpy = spyOn(inputswitch, 'onClick').and.callThrough(); - const onModelChangeSpy = spyOn(inputswitch, 'onModelChange').and.callThrough(); - const inputSwitchEl = fixture.debugElement.query(By.css('div')).nativeElement; - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - inputSwitchEl.click(); - fixture.detectChanges(); - - expect(inputSwitchEl.className).toContain('p-disabled'); - expect(inputEl.disabled).toEqual(true); - expect(onClickSpy).toHaveBeenCalled(); - expect(onModelChangeSpy).not.toHaveBeenCalled(); + it('should render the ToggleSwitch with default values', () => { + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + expect(inputElement).toBeTruthy(); + expect(inputElement.nativeElement.checked).toBeFalse(); }); - it('should change style and styleClass', () => { - inputswitch.style = { height: '300px' }; - inputswitch.styleClass = 'Primeng ROCKS!'; + it('should reflect the correct state when checked', () => { + toggleSwitch.modelValue.set(true); fixture.detectChanges(); - - const inputSwitchEl = fixture.debugElement.query(By.css('div')).nativeElement; - expect(inputSwitchEl.className).toContain('Primeng ROCKS!'); - expect(inputSwitchEl.style.height).toContain('300px'); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + expect(inputElement.nativeElement.checked).toBeTrue(); }); - it('should get a name inputId and tabindex', () => { - inputswitch.tabindex = 5; - inputswitch.inputId = 'Primeng!'; - inputswitch.name = 'Primeng ROCKS!'; + it('should not allow interaction when disabled', () => { + toggleSwitch.disabled.set(true); fixture.detectChanges(); - - const inputSwitchEl = fixture.debugElement.query(By.css('input')).nativeElement; - expect(inputSwitchEl.tabIndex).toEqual(5); - expect(inputSwitchEl.name).toEqual('Primeng ROCKS!'); - expect(inputSwitchEl.id).toEqual('Primeng!'); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + expect(inputElement.nativeElement.disabled).toBeTrue(); }); - it('should checked when click', () => { + it('should autofocus the input element if autofocus is true', () => { + toggleSwitchRef.setInput('autofocus', true); fixture.detectChanges(); - - const onClickSpy = spyOn(inputswitch, 'onClick').and.callThrough(); - const inputSwitchEl = fixture.debugElement.query(By.css('div')).nativeElement; - let data; - inputswitch.onChange.subscribe((value) => (data = value)); - inputSwitchEl.click(); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + inputElement.nativeElement.focus(); // Manually trigger focus fixture.detectChanges(); - expect(inputswitch.checked()).toEqual(true); - expect(data.checked).toEqual(true); - expect(onClickSpy).toHaveBeenCalled(); + expect(document.activeElement).toBe(inputElement.nativeElement); }); - it('should listen event emitter', () => { + it('should toggle the checked state on click and emit onChange', () => { + spyOn(toggleSwitch.onChange, 'emit'); fixture.detectChanges(); - let data; - inputswitch.onChange.subscribe((value) => (data = value)); - const inputSwitchEl = fixture.debugElement.query(By.css('div')).nativeElement; - inputSwitchEl.click(); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + inputElement.nativeElement.click(); fixture.detectChanges(); - expect(data.checked).toEqual(true); - inputSwitchEl.click(); - expect(data.checked).toEqual(false); + expect(toggleSwitch.modelValue()).toBeTrue(); + expect(toggleSwitch.onChange.emit).toHaveBeenCalled(); }); - it('should change focused', () => { + it('should not toggle when readonly is true', () => { + toggleSwitchRef.setInput('readonly', true); fixture.detectChanges(); - const onFocusSpy = spyOn(inputswitch, 'onFocus').and.callThrough(); - const onBlurSpy = spyOn(inputswitch, 'onBlur').and.callThrough(); - const onModelTouchedSpy = spyOn(inputswitch, 'onModelTouched').and.callThrough(); - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - const inputSwitchEl = fixture.debugElement.query(By.css('div')).nativeElement; - inputEl.dispatchEvent(new Event('focus')); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + inputElement.nativeElement.click(); fixture.detectChanges(); - expect(inputSwitchEl.className).toContain('p-focus'); - expect(inputswitch.focused).toEqual(true); - expect(onFocusSpy).toHaveBeenCalled(); - inputEl.dispatchEvent(new Event('blur')); - fixture.detectChanges(); - - expect(inputswitch.focused).toEqual(false); - expect(inputSwitchEl.className).not.toContain('p-focus'); - expect(onBlurSpy).toHaveBeenCalled(); - expect(onModelTouchedSpy).toHaveBeenCalled(); + expect(toggleSwitch.modelValue()).toBeFalse(); // default state }); - it('should change disabled', () => { + it('should apply correct aria-label and aria-labelledby attributes', () => { + toggleSwitchRef.setInput('ariaLabel', 'Toggle Switch'); + toggleSwitchRef.setInput('ariaLabelledBy', 'toggleSwitchLabel'); fixture.detectChanges(); - inputswitch.setDisabledState(true); - fixture.detectChanges(); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + expect(inputElement.nativeElement.getAttribute('aria-label')).toBe('Toggle Switch'); + expect(inputElement.nativeElement.getAttribute('aria-labelledby')).toBe('toggleSwitchLabel'); + }); - const onClickSpy = spyOn(inputswitch, 'onClick').and.callThrough(); - const onModelChangeSpy = spyOn(inputswitch, 'onModelChange').and.callThrough(); - const inputSwitchEl = fixture.debugElement.query(By.css('div')).nativeElement; - const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; - inputSwitchEl.click(); + it('should apply the correct tabindex attribute', () => { + toggleSwitchRef.setInput('tabindex', 3); fixture.detectChanges(); - expect(inputSwitchEl.className).toContain('p-disabled'); - expect(inputEl.disabled).toEqual(true); - expect(onClickSpy).toHaveBeenCalled(); - expect(onModelChangeSpy).not.toHaveBeenCalled(); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + expect(inputElement.nativeElement.getAttribute('tabindex')).toBe('3'); // Should reflect tabindex value }); - it('should toggle the modelValue and call necessary functions when not disabled and not readonly', () => { - const onClickSpy = spyOn(inputswitch, 'onClick').and.callThrough(); - const onChangeSpy = spyOn(inputswitch.onChange, 'emit').and.callThrough(); - const onModelChangeSpy = spyOn(inputswitch, 'onModelChange').and.callThrough(); - - const divElement: HTMLElement = fixture.debugElement.query(By.css('div')).nativeElement; - divElement.click(); + it('should initialize with the correct checked state based on modelValue', () => { + toggleSwitch.modelValue.set(true); // Set initial value to true fixture.detectChanges(); - expect(onClickSpy).toHaveBeenCalledWith(jasmine.anything()); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + expect(inputElement.nativeElement.checked).toBeTrue(); // Should reflect true state + }); - const initialModelValue = inputswitch.modelValue; - inputswitch.onClick(new Event('click')); + it('should emit onChange event with the correct value on click', () => { + spyOn(fixture.componentInstance.onChange, 'emit'); + fixture.detectChanges(); - expect(inputswitch.modelValue).toEqual(initialModelValue ? inputswitch.falseValue : inputswitch.trueValue); - expect(onModelChangeSpy).toHaveBeenCalled(); - expect(onChangeSpy).toHaveBeenCalledWith({ - originalEvent: jasmine.anything(), - checked: inputswitch.modelValue - }); - }); + const inputElement = fixture.debugElement.query(By.css('input[type="checkbox"]')); + inputElement.nativeElement.click(); // Toggle to true + fixture.detectChanges(); - it('should not toggle the modelValue when disabled or readonly', () => { - inputswitch.disabled = true; - let initialModelValue = inputswitch.modelValue; - inputswitch.onClick(new Event('click')); - expect(inputswitch.modelValue).toEqual(initialModelValue); - - inputswitch.disabled = false; - inputswitch.readonly = true; - initialModelValue = inputswitch.modelValue; - inputswitch.onClick(new Event('click')); - expect(inputswitch.modelValue).toEqual(initialModelValue); + expect(toggleSwitch.modelValue()).toBeTrue(); + expect(toggleSwitch.onChange.emit).toHaveBeenCalledWith({ originalEvent: jasmine.any(Event), checked: true }); }); }); diff --git a/packages/primeng/src/toggleswitch/toggleswitch.ts b/packages/primeng/src/toggleswitch/toggleswitch.ts index b740c1fbd10..b711957591f 100755 --- a/packages/primeng/src/toggleswitch/toggleswitch.ts +++ b/packages/primeng/src/toggleswitch/toggleswitch.ts @@ -1,7 +1,28 @@ -import { CommonModule } from '@angular/common'; -import { AfterContentInit, booleanAttribute, ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, forwardRef, inject, Input, NgModule, numberAttribute, Output, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core'; +import { NgClass, NgStyle, NgTemplateOutlet } from '@angular/common'; +import { + booleanAttribute, + ChangeDetectionStrategy, + Component, + contentChild, + contentChildren, + ElementRef, + OutputEmitterRef, + forwardRef, + inject, + input, + model, + signal, + computed, + NgModule, + numberAttribute, + output, + viewChild, + TemplateRef, + ViewEncapsulation, + WritableSignal +} from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { SharedModule } from 'primeng/api'; +import { PrimeTemplate, SharedModule } from 'primeng/api'; import { AutoFocus } from 'primeng/autofocus'; import { BaseComponent } from 'primeng/basecomponent'; import { ToggleSwitchStyle } from './style/toggleswitchstyle'; @@ -28,31 +49,31 @@ export const TOGGLESWITCH_VALUE_ACCESSOR: any = { @Component({ selector: 'p-toggleswitch, p-toggleSwitch, p-toggle-switch', standalone: true, - imports: [CommonModule, AutoFocus, SharedModule], + imports: [NgClass, NgStyle, NgTemplateOutlet, AutoFocus, SharedModule], template: ` -
+
- @if (handleTemplate) { - + @if (customHandleTemplate()) { + }
@@ -62,75 +83,75 @@ export const TOGGLESWITCH_VALUE_ACCESSOR: any = { changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }) -export class ToggleSwitch extends BaseComponent implements AfterContentInit { +export class ToggleSwitch extends BaseComponent { /** * Inline style of the component. * @group Props */ - @Input() style: { [klass: string]: any } | null | undefined; + style = input<{ [klass: string]: any } | null>(); /** * Style class of the component. * @group Props */ - @Input() styleClass: string | undefined; + styleClass = input(); /** * Index of the element in tabbing order. * @group Props */ - @Input({ transform: numberAttribute }) tabindex: number | undefined; + tabindex = input(undefined, { transform: numberAttribute }); /** * Identifier of the input element. * @group Props */ - @Input() inputId: string | undefined; + inputId = input(); /** * Name of the input element. * @group Props */ - @Input() name: string | undefined; + name = input(); /** * When present, it specifies that the element should be disabled. * @group Props */ - @Input({ transform: booleanAttribute }) disabled: boolean | undefined; + disabled = model(); /** * When present, it specifies that the component cannot be edited. * @group Props */ - @Input({ transform: booleanAttribute }) readonly: boolean | undefined; + readonly = input(undefined, { transform: booleanAttribute }); /** * Value in checked state. * @group Props */ - @Input() trueValue: any = true; + trueValue = input(true); /** * Value in unchecked state. * @group Props */ - @Input() falseValue: any = false; + falseValue = input(false); /** * Used to define a string that autocomplete attribute the current element. * @group Props */ - @Input() ariaLabel: string | undefined; + ariaLabel = input(); /** * Establishes relationships between the component and label(s) where its value should be one or more element IDs. * @group Props */ - @Input() ariaLabelledBy: string | undefined; + ariaLabelledBy = input(); /** * When present, it specifies that the component should automatically get focus on load. * @group Props */ - @Input({ transform: booleanAttribute }) autofocus: boolean | undefined; + autofocus = input(undefined, { transform: booleanAttribute }); /** * Callback to invoke when the on value change. * @param {ToggleSwitchChangeEvent} event - Custom change event. * @group Emits */ - @Output() onChange: EventEmitter = new EventEmitter(); + onChange: OutputEmitterRef = output(); - @ViewChild('input') input!: ElementRef; + input = viewChild.required('input'); /** * Callback to invoke when the on value change. * @type {TemplateRef} context - Context of the template @@ -141,11 +162,30 @@ export class ToggleSwitch extends BaseComponent implements AfterContentInit { * @see {@link ToggleSwitchHandleTemplateContext} * @group Templates */ - @ContentChild('handle') handleTemplate: TemplateRef | undefined; + handleTemplate = contentChild | undefined>('handle'); + /** + * List of PrimeTemplate instances provided by the content. + * @group Templates + */ + sTemplates = contentChildren(PrimeTemplate); + /** + * Computes the custom input template if available. + * @returns {TemplateRef | undefined} The custom input template or undefined if not available. + */ + customHandleTemplate = computed>(() => { + if (this.sTemplates()) { + const templates = this.sTemplates().reduce<{ [key: string]: TemplateRef }>((prev, curr) => { + prev[curr.getType()] = curr.template; + return prev; + }, {}); + return templates['handle']; + } + return this.handleTemplate(); + }); - modelValue: any = false; + modelValue: WritableSignal = signal(false); - focused: boolean = false; + focused: WritableSignal = signal(false); onModelChange: Function = () => {}; @@ -153,42 +193,26 @@ export class ToggleSwitch extends BaseComponent implements AfterContentInit { _componentStyle = inject(ToggleSwitchStyle); - ngAfterContentInit() { - this.templates.forEach((item) => { - switch (item.getType()) { - case 'handle': - this.handleTemplate = item.template; - break; - } - }); - } - onClick(event: Event) { - if (!this.disabled && !this.readonly) { - this.modelValue = this.checked() ? this.falseValue : this.trueValue; - - this.onModelChange(this.modelValue); - this.onChange.emit({ - originalEvent: event, - checked: this.modelValue - }); - - this.input.nativeElement.focus(); + if (!this.disabled() && !this.readonly()) { + this.modelValue.set(this.checked() ? this.falseValue() : this.trueValue()); + this.onModelChange(this.modelValue()); + this.onChange.emit({ originalEvent: event, checked: this.modelValue() }); + this.input().nativeElement.focus(); } } onFocus() { - this.focused = true; + this.focused.set(true); } onBlur() { - this.focused = false; + this.focused.set(false); this.onModelTouched(); } writeValue(value: any): void { - this.modelValue = value; - this.cd.markForCheck(); + this.modelValue.set(value); } registerOnChange(fn: Function): void { @@ -200,13 +224,10 @@ export class ToggleSwitch extends BaseComponent implements AfterContentInit { } setDisabledState(val: boolean): void { - this.disabled = val; - this.cd.markForCheck(); + this.disabled.set(val); } - checked() { - return this.modelValue === this.trueValue; - } + checked = computed(() => this.modelValue() === this.trueValue()); } @NgModule({