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 { DOCUMENT } from '@angular/common'; |
||||
import { HttpClient, HttpClientModule } from '@angular/common/http'; |
import { Router } from '@angular/router'; |
||||
import { ElementRef, Renderer2 } from '@angular/core'; |
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; |
||||
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; |
import { Pipe, PipeTransform } from '@angular/core'; |
||||
import { Subject } from 'rxjs'; |
import { Subject } from 'rxjs'; |
||||
|
import { vi } from 'vitest'; |
||||
|
|
||||
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; |
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', () => { |
beforeAll(async () => { |
||||
let spectator: SpectatorHost<HttpErrorWrapperComponent>; |
await setupComponentResources( |
||||
const createHost = createHostFactory({ |
'../components/http-error-wrapper', |
||||
component: HttpErrorWrapperComponent, |
import.meta.url, |
||||
declarations: [], |
); |
||||
mocks: [HttpClient], |
|
||||
providers: [ |
|
||||
{ provide: CORE_OPTIONS, useValue: {} }, |
|
||||
{ provide: Renderer2, useValue: { removeChild: () => null } }, |
|
||||
{ |
|
||||
provide: ElementRef, |
|
||||
useValue: { nativeElement: document.createElement('div') }, |
|
||||
}, |
|
||||
], |
|
||||
imports: [HttpClientModule, LocalizationPipe], |
|
||||
}); |
}); |
||||
|
|
||||
beforeEach(() => { |
beforeEach(() => { |
||||
spectator = createHost( |
if (!createComponent) { |
||||
'<abp-http-error-wrapper title="_::Oops!" details="_::Sorry, an error has occured."></abp-http-error-wrapper>', |
createComponent = createComponentFactory({ |
||||
); |
component: HttpErrorWrapperComponent, |
||||
spectator.component.destroy$ = new Subject(); |
detectChanges: false, |
||||
}); |
|
||||
|
|
||||
describe('#destroy', () => { |
|
||||
it('should be call when pressed the esc key', done => { |
|
||||
spectator.component.destroy$.subscribe(() => { |
|
||||
done(); |
|
||||
}); |
|
||||
|
|
||||
spectator.keyboard.pressEscape(); |
overrideComponents: [ |
||||
}); |
[ |
||||
|
HttpErrorWrapperComponent, |
||||
|
{ |
||||
|
set: { |
||||
|
template: '<div></div>', |
||||
|
imports: [MockLocalizationPipe], |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
], |
||||
|
|
||||
it('should be call when clicked the close button', done => { |
providers: [ |
||||
spectator.component.destroy$.subscribe(() => { |
{ |
||||
done(); |
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'; |
import '@angular/compiler'; |
||||
setupZoneTestEnv(); |
import 'zone.js'; |
||||
|
import 'zone.js/testing'; |
||||
const originalError = console.error; |
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; |
||||
console.error = (...args: any[]) => { |
import { |
||||
if (args[0]?.includes?.('ExpressionChangedAfterItHasBeenCheckedError')) { |
ɵgetCleanupHook as getCleanupHook, |
||||
return; |
getTestBed |
||||
} |
} from '@angular/core/testing'; |
||||
originalError.apply(console, args); |
|
||||
}; |
|
||||
|
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