diff --git a/components/affix/affix.spec.ts b/components/affix/affix.spec.ts index ed59b6e9e2..bcda2bcc23 100644 --- a/components/affix/affix.spec.ts +++ b/components/affix/affix.spec.ts @@ -1,9 +1,13 @@ -import { BidiModule, Dir } from '@angular/cdk/bidi'; -import { Component, DebugElement, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; +import { BidiModule, Dir, Directionality } from '@angular/cdk/bidi'; +import { Platform } from '@angular/cdk/platform'; +import { DOCUMENT } from '@angular/common'; +import { Component, DebugElement, ElementRef, Renderer2, ViewChild } from '@angular/core'; +import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { Subject, Subscription } from 'rxjs'; import { NzScrollService } from 'ng-zorro-antd/core/services'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; import { NzAffixComponent } from './affix.component'; import { NzAffixModule } from './affix.module'; @@ -575,3 +579,182 @@ export class TestAffixRtlComponent { newOffset!: number; newOffsetBottom!: number; } + +class MockDirectionality { + value = 'ltr'; + change = new Subject(); +} +describe('NzAffixComponent', () => { + let component: NzAffixComponent; + let fixture: ComponentFixture; + let mockRenderer: Renderer2; + let mockPlatform: Platform; + let mockDirectionality: MockDirectionality; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + NzAffixComponent, + NzScrollService, + { provide: Renderer2, useValue: jasmine.createSpyObj('Renderer2', ['setStyle', 'addClass', 'removeClass']) }, + { provide: ElementRef, useValue: new ElementRef(document.createElement('div')) }, + { provide: DOCUMENT, useValue: document }, + { provide: Directionality, useClass: MockDirectionality }, + { provide: Platform, useValue: { isBrowser: true } } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(NzAffixComponent); + component = fixture.componentInstance; + mockRenderer = TestBed.inject(Renderer2); + mockPlatform = TestBed.inject(Platform); + mockDirectionality = TestBed.inject(Directionality) as unknown as MockDirectionality; + }); + + afterEach(() => { + mockDirectionality.change.complete(); + }); + + it('should handle directionality change', () => { + mockDirectionality.value = 'ltr'; + component.ngOnInit(); + + spyOn(component, 'registerListeners'); + spyOn(component, 'updatePosition'); + spyOn(component['cdr'], 'detectChanges'); + + mockDirectionality.change.next('rtl'); + + expect(component.dir).toBe('rtl'); + expect(component['registerListeners']).toHaveBeenCalled(); + expect(component.updatePosition).toHaveBeenCalled(); + expect(component['cdr'].detectChanges).toHaveBeenCalled(); + }); + + it('should register listeners if platform is browser', () => { + spyOn(component as NzSafeAny, 'removeListeners').and.callThrough(); + spyOn(mockRenderer, 'setStyle'); + + component.ngOnInit(); + component.ngAfterViewInit(); + + expect(component['removeListeners']).toHaveBeenCalled(); + expect(component['positionChangeSubscription']).toBeDefined(); + expect(component['timeout']).toBeDefined(); + }); + + it('should not register listeners if platform is not browser', () => { + mockPlatform.isBrowser = false; + + component.ngOnInit(); + component.ngAfterViewInit(); + + expect(component['positionChangeSubscription']).toEqual(Subscription.EMPTY); + }); + + it('should remove listeners on destroy', () => { + spyOn(component as NzSafeAny, 'removeListeners').and.callThrough(); + + component.ngOnDestroy(); + + expect(component['removeListeners']).toHaveBeenCalled(); + }); + + it('should update position correctly', () => { + spyOn(component, 'setAffixStyle').and.callThrough(); + spyOn(component, 'setPlaceholderStyle').and.callThrough(); + + const event = new Event('scroll'); + component.updatePosition(event); + + expect(component['setAffixStyle']).toHaveBeenCalled(); + expect(component['setPlaceholderStyle']).toHaveBeenCalled(); + }); + + it('should update RTL class when direction changes', () => { + component['fixedEl'].nativeElement.classList.add('ant-affix'); + component.dir = 'ltr'; + component['updateRtlClass'](); + fixture.detectChanges(); + + expect(component['fixedEl'].nativeElement.classList.contains('ant-affix-rtl')).toBeFalse(); + + component.dir = 'rtl'; + component['updateRtlClass'](); + fixture.detectChanges(); + + expect(component['fixedEl'].nativeElement.classList.contains('ant-affix-rtl')).toBeTrue(); + + component.dir = 'ltr'; + component['updateRtlClass'](); + fixture.detectChanges(); + + expect(component['fixedEl'].nativeElement.classList.contains('ant-affix-rtl')).toBeFalse(); + + component.dir = 'rtl'; + component['fixedEl'].nativeElement.classList.remove('ant-affix'); + component['fixedEl'].nativeElement.classList.add('ant-affix-rtl'); + component['updateRtlClass'](); + fixture.detectChanges(); + + expect(component['fixedEl'].nativeElement.classList.contains('ant-affix-rtl')).toBeFalse(); + }); + + it('should not perform position updates if platform is not browser', () => { + mockPlatform.isBrowser = false; + spyOn(component, 'getOffset'); + + component.updatePosition(new Event('scroll')); + + expect(component['getOffset']).not.toHaveBeenCalled(); + }); + + it('should update affixStyle with new width on resize event', () => { + mockPlatform.isBrowser = true; + spyOn(component, 'getOffset').and.returnValue({ + top: 0, + left: 0, + width: 100, + height: 50 + }); + spyOn(component, 'setAffixStyle').and.callThrough(); + component.nzOffsetTop = 10; + component.nzOffsetBottom = 10; + + component.updatePosition(new Event('resize')); + + expect(component['setAffixStyle']).toHaveBeenCalledWith( + jasmine.any(Event), + jasmine.objectContaining({ width: 100 }) + ); + }); + + it('should update the affix style with the correct width on resize', () => { + spyOn(component, 'setAffixStyle').and.callThrough(); + + const scrollTop = 40; + spyOn(component['scrollSrv'], 'getScroll').and.returnValue(scrollTop); + const elemOffset = { top: 200, left: 0, width: 200, height: 50 }; + spyOn(component, 'getOffset').and.returnValue(elemOffset); + component['nzOffsetTop'] = 150; + component['nzOffsetBottom'] = 50; + spyOnProperty(component['placeholderNode'], 'offsetWidth').and.returnValue(120); + + component['affixStyle'] = { + position: 'fixed', + top: '10px', + left: '10px', + width: '100px' + }; + + const mockEvent = new Event('resize'); + component.updatePosition(mockEvent); + + expect(component['setAffixStyle']).toHaveBeenCalledWith(mockEvent, { + position: 'fixed', + top: '10px', + left: '10px', + width: 120 + }); + }); +});