1+ /**
2+ * Memory leak test for nz-tabset components
3+ * This test specifically targets the animation-related memory leak
4+ */
5+
6+ import { Component , DebugElement } from '@angular/core' ;
7+ import { ComponentFixture , TestBed , fakeAsync , tick } from '@angular/core/testing' ;
8+ import { By } from '@angular/platform-browser' ;
9+ import { provideAnimations } from '@angular/platform-browser/animations' ;
10+
11+ import { NzTabsModule } from './tabs.module' ;
12+ import { NzTabsComponent } from './tabs.component' ;
13+
14+ describe ( 'NzTabs Memory Leak' , ( ) => {
15+ beforeEach ( ( ) => {
16+ TestBed . configureTestingModule ( {
17+ providers : [ provideAnimations ( ) ]
18+ } ) ;
19+ } ) ;
20+
21+ it ( 'should not retain detached elements in memory when tabs are destroyed' , fakeAsync ( ( ) => {
22+ const fixture = TestBed . createComponent ( MemoryLeakTestComponent ) ;
23+
24+ // Track the initial number of tabpane elements
25+ const initialElements = document . querySelectorAll ( '.ant-tabs-tabpane' ) . length ;
26+
27+ // Create and destroy tabs multiple times to simulate the memory leak
28+ for ( let i = 0 ; i < 5 ; i ++ ) {
29+ fixture . componentInstance . showTabs = true ;
30+ fixture . detectChanges ( ) ;
31+ tick ( 300 ) ; // Wait for enter animations
32+
33+ fixture . componentInstance . showTabs = false ;
34+ fixture . detectChanges ( ) ;
35+ tick ( 300 ) ; // Wait for leave animations
36+ }
37+
38+ // Check that no tabpane elements are retained in the DOM
39+ const finalElements = document . querySelectorAll ( '.ant-tabs-tabpane' ) . length ;
40+ expect ( finalElements ) . toBe ( initialElements ) ;
41+
42+ // Check for any detached DOM nodes that might still be in memory
43+ const hiddenElements = document . querySelectorAll ( '.ant-tabs-tabpane[style*="display: none"]' ) ;
44+ expect ( hiddenElements . length ) . toBe ( 0 ) ;
45+ } ) ) ;
46+
47+ it ( 'should properly handle tab switching animations without memory leaks' , fakeAsync ( ( ) => {
48+ const fixture = TestBed . createComponent ( MemoryLeakTestComponent ) ;
49+
50+ fixture . componentInstance . showTabs = true ;
51+ fixture . detectChanges ( ) ;
52+
53+ const tabsComponent : NzTabsComponent = fixture . debugElement . query ( By . directive ( NzTabsComponent ) ) ?. componentInstance ;
54+
55+ if ( tabsComponent ) {
56+ // Switch between tabs multiple times to trigger animations
57+ for ( let i = 0 ; i < 3 ; i ++ ) {
58+ tabsComponent . nzSelectedIndex = 1 ;
59+ fixture . detectChanges ( ) ;
60+ tick ( 300 ) ;
61+
62+ tabsComponent . nzSelectedIndex = 0 ;
63+ fixture . detectChanges ( ) ;
64+ tick ( 300 ) ;
65+ }
66+ }
67+
68+ // Verify no animations are stuck in progress
69+ const elementsWithAbsolutePosition = document . querySelectorAll ( '[style*="position: absolute"]' ) ;
70+ const tabRelatedAbsoluteElements = Array . from ( elementsWithAbsolutePosition ) . filter ( el =>
71+ el . classList . contains ( 'ant-tabs-tabpane' ) ||
72+ el . closest ( '.ant-tabs' )
73+ ) ;
74+
75+ // Only elements that are properly part of the active DOM should have absolute positioning
76+ // Not detached elements from animations
77+ expect ( tabRelatedAbsoluteElements . length ) . toBe ( 0 ) ;
78+ } ) ) ;
79+
80+ it ( 'should handle rapid component creation and destruction' , fakeAsync ( ( ) => {
81+ const fixture = TestBed . createComponent ( MemoryLeakTestComponent ) ;
82+
83+ // Rapid create/destroy cycles to stress test the cleanup
84+ for ( let i = 0 ; i < 10 ; i ++ ) {
85+ fixture . componentInstance . showTabs = true ;
86+ fixture . detectChanges ( ) ;
87+ tick ( 50 ) ; // Very short tick for rapid testing
88+
89+ fixture . componentInstance . showTabs = false ;
90+ fixture . detectChanges ( ) ;
91+ tick ( 50 ) ;
92+ }
93+
94+ // Final check for accumulated elements
95+ const remainingTabElements = document . querySelectorAll ( '.ant-tabs' ) ;
96+ expect ( remainingTabElements . length ) . toBe ( 0 ) ;
97+
98+ // Ensure no animation artifacts remain
99+ const tabPaneElements = document . querySelectorAll ( '.ant-tabs-tabpane' ) ;
100+ expect ( tabPaneElements . length ) . toBe ( 0 ) ;
101+ } ) ) ;
102+ } ) ;
103+
104+ @Component ( {
105+ imports : [ NzTabsModule ] ,
106+ template : `
107+ @if (showTabs) {
108+ <nz-tabs [nzAnimated]="animated" [(nzSelectedIndex)]="selectedIndex">
109+ <nz-tab nzTitle="Tab 1">
110+ <div class="tab-content">Content of Tab 1</div>
111+ </nz-tab>
112+ <nz-tab nzTitle="Tab 2">
113+ <div class="tab-content">Content of Tab 2</div>
114+ </nz-tab>
115+ <nz-tab nzTitle="Tab 3">
116+ <div class="tab-content">Content of Tab 3</div>
117+ </nz-tab>
118+ </nz-tabs>
119+ }
120+ `
121+ } )
122+ class MemoryLeakTestComponent {
123+ showTabs = false ;
124+ animated = true ;
125+ selectedIndex = 0 ;
126+ }
0 commit comments