mirror of https://github.com/abpframework/abp.git
18 changed files with 482 additions and 244 deletions
@ -1,49 +1,84 @@ |
|||
import { CORE_OPTIONS, LocalizationPipe } from '@abp/ng.core'; |
|||
import { HttpClient, HttpClientModule } from '@angular/common/http'; |
|||
import { ElementRef, Renderer2 } from '@angular/core'; |
|||
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; |
|||
import { DOCUMENT } from '@angular/common'; |
|||
import { Router } from '@angular/router'; |
|||
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; |
|||
import { Pipe, PipeTransform } from '@angular/core'; |
|||
import { Subject } from 'rxjs'; |
|||
import { vi } from 'vitest'; |
|||
|
|||
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; |
|||
import { setupComponentResources } from './test-utils'; |
|||
|
|||
/** |
|||
* Mock pipe to avoid ABP DI chain |
|||
*/ |
|||
@Pipe({ name: 'abpLocalization'}) |
|||
class MockLocalizationPipe implements PipeTransform { |
|||
transform(value: any): any { |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
describe('HttpErrorWrapperComponent', () => { |
|||
let spectator: Spectator<HttpErrorWrapperComponent>; |
|||
let createComponent: ReturnType<typeof createComponentFactory<HttpErrorWrapperComponent>>; |
|||
|
|||
describe('ErrorComponent', () => { |
|||
let spectator: SpectatorHost<HttpErrorWrapperComponent>; |
|||
const createHost = createHostFactory({ |
|||
component: HttpErrorWrapperComponent, |
|||
declarations: [], |
|||
mocks: [HttpClient], |
|||
providers: [ |
|||
{ provide: CORE_OPTIONS, useValue: {} }, |
|||
{ provide: Renderer2, useValue: { removeChild: () => null } }, |
|||
{ |
|||
provide: ElementRef, |
|||
useValue: { nativeElement: document.createElement('div') }, |
|||
}, |
|||
], |
|||
imports: [HttpClientModule, LocalizationPipe], |
|||
beforeAll(async () => { |
|||
await setupComponentResources( |
|||
'../components/http-error-wrapper', |
|||
import.meta.url, |
|||
); |
|||
}); |
|||
|
|||
beforeEach(() => { |
|||
spectator = createHost( |
|||
'<abp-http-error-wrapper title="_::Oops!" details="_::Sorry, an error has occured."></abp-http-error-wrapper>', |
|||
); |
|||
spectator.component.destroy$ = new Subject(); |
|||
}); |
|||
|
|||
describe('#destroy', () => { |
|||
it('should be call when pressed the esc key', done => { |
|||
spectator.component.destroy$.subscribe(() => { |
|||
done(); |
|||
}); |
|||
beforeEach(() => { |
|||
if (!createComponent) { |
|||
createComponent = createComponentFactory({ |
|||
component: HttpErrorWrapperComponent, |
|||
detectChanges: false, |
|||
|
|||
spectator.keyboard.pressEscape(); |
|||
}); |
|||
overrideComponents: [ |
|||
[ |
|||
HttpErrorWrapperComponent, |
|||
{ |
|||
set: { |
|||
template: '<div></div>', |
|||
imports: [MockLocalizationPipe], |
|||
}, |
|||
}, |
|||
], |
|||
], |
|||
|
|||
it('should be call when clicked the close button', done => { |
|||
spectator.component.destroy$.subscribe(() => { |
|||
done(); |
|||
providers: [ |
|||
{ |
|||
provide: DOCUMENT, |
|||
useValue: document, |
|||
}, |
|||
{ |
|||
provide: Router, |
|||
useValue: { |
|||
navigateByUrl: vi.fn(), |
|||
}, |
|||
}, |
|||
], |
|||
}); |
|||
} |
|||
|
|||
spectator = createComponent(); |
|||
|
|||
spectator.component.destroy$ = new Subject<void>(); |
|||
spectator.component.title = '_::Oops!'; |
|||
spectator.component.details = '_::Sorry, an error has occured.'; |
|||
}); |
|||
|
|||
it('should create component', () => { |
|||
expect(spectator.component).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should emit destroy$ when destroy is called', () => { |
|||
const spy = vi.fn(); |
|||
spectator.component.destroy$.subscribe(spy); |
|||
|
|||
spectator.component.destroy(); |
|||
|
|||
spectator.click('#abp-close-button'); |
|||
}); |
|||
expect(spy).toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
|
|||
@ -0,0 +1,54 @@ |
|||
import { readFileSync } from 'node:fs'; |
|||
import { resolve, dirname } from 'node:path'; |
|||
import { fileURLToPath } from 'node:url'; |
|||
|
|||
/** |
|||
* Sets up component resource resolution for Angular component tests. |
|||
* This is needed when components have external templates or stylesheets. |
|||
* |
|||
* @param componentDirPath - The path to the component directory relative to the test file. |
|||
* For example: '../components/loader-bar' or './components/my-component' |
|||
* @param testFileUrl - The import.meta.url from the test file. Defaults to the caller's location. |
|||
* |
|||
* @example |
|||
* ```typescript
|
|||
* |
|||
* import { setupComponentResources } from './test-utils'; |
|||
* |
|||
* beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url)); |
|||
* ``` |
|||
*/ |
|||
export async function setupComponentResources( |
|||
componentDirPath: string, |
|||
testFileUrl: string = import.meta.url, |
|||
): Promise<void> { |
|||
try { |
|||
if (typeof process !== 'undefined' && process.versions?.node) { |
|||
const { ɵresolveComponentResources: resolveComponentResources } = await import('@angular/core'); |
|||
|
|||
// Get the test file directory path
|
|||
const testFileDir = dirname(fileURLToPath(testFileUrl)); |
|||
const componentDir = resolve(testFileDir, componentDirPath); |
|||
|
|||
await resolveComponentResources((url: string) => { |
|||
// For SCSS/SASS files, return empty CSS since jsdom can't parse SCSS
|
|||
if (url.endsWith('.scss') || url.endsWith('.sass')) { |
|||
return Promise.resolve(''); |
|||
} |
|||
|
|||
// For other files (HTML, CSS, etc.), read the actual content
|
|||
try { |
|||
// Resolve relative paths like './component.scss' or 'component.scss'
|
|||
const normalizedUrl = url.replace(/^\.\//, ''); |
|||
const filePath = resolve(componentDir, normalizedUrl); |
|||
return Promise.resolve(readFileSync(filePath, 'utf-8')); |
|||
} catch (error) { |
|||
// If file not found, return empty string
|
|||
return Promise.resolve(''); |
|||
} |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.warn('Failed to set up component resource resolver:', error); |
|||
} |
|||
} |
|||
@ -1,10 +1,28 @@ |
|||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; |
|||
setupZoneTestEnv(); |
|||
|
|||
const originalError = console.error; |
|||
console.error = (...args: any[]) => { |
|||
if (args[0]?.includes?.('ExpressionChangedAfterItHasBeenCheckedError')) { |
|||
return; |
|||
} |
|||
originalError.apply(console, args); |
|||
}; |
|||
import '@angular/compiler'; |
|||
import 'zone.js'; |
|||
import 'zone.js/testing'; |
|||
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; |
|||
import { |
|||
ɵgetCleanupHook as getCleanupHook, |
|||
getTestBed |
|||
} from '@angular/core/testing'; |
|||
|
|||
|
|||
beforeEach(getCleanupHook(false)); |
|||
afterEach(getCleanupHook(true)); |
|||
|
|||
// Initialize Angular testing environment
|
|||
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); |
|||
|
|||
|
|||
// Mock window.location for test environment
|
|||
Object.defineProperty(window, 'location', { |
|||
value: { |
|||
href: 'http://localhost:4200', |
|||
origin: 'http://localhost:4200', |
|||
pathname: '/', |
|||
search: '', |
|||
hash: '', |
|||
}, |
|||
writable: true, |
|||
}); |
|||
|
|||
Loading…
Reference in new issue