mirror of https://github.com/abpframework/abp.git
153 changed files with 46224 additions and 1971 deletions
@ -1,6 +1,6 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Http.Client.Proxying |
|||
namespace Volo.Abp.Http.Client.ClientProxying |
|||
{ |
|||
public class ApiVersionInfo //TODO: Rename to not conflict with api versioning apis
|
|||
{ |
|||
@ -0,0 +1,19 @@ |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Http.Client.ClientProxying; |
|||
|
|||
namespace Volo.Abp.Http.Client.DynamicProxying |
|||
{ |
|||
public class DynamicHttpProxyInterceptorClientProxy<TService> : ClientProxyBase<TService> |
|||
{ |
|||
public virtual async Task<T> CallRequestAsync<T>(ClientProxyRequestContext requestContext) |
|||
{ |
|||
return await base.RequestAsync<T>(requestContext); |
|||
} |
|||
|
|||
public virtual async Task<HttpContent> CallRequestAsync(ClientProxyRequestContext requestContext) |
|||
{ |
|||
return await base.RequestAsync(requestContext); |
|||
} |
|||
} |
|||
} |
|||
@ -1,267 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
using System.Net.Http.Headers; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.Extensions.Primitives; |
|||
using Volo.Abp.Content; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client.Authentication; |
|||
using Volo.Abp.Http.Modeling; |
|||
using Volo.Abp.Http.ProxyScripting.Generators; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Threading; |
|||
using Volo.Abp.Tracing; |
|||
|
|||
namespace Volo.Abp.Http.Client.Proxying |
|||
{ |
|||
public class HttpProxyExecuter : IHttpProxyExecuter, ITransientDependency |
|||
{ |
|||
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
|||
protected ICorrelationIdProvider CorrelationIdProvider { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; } |
|||
protected IProxyHttpClientFactory HttpClientFactory { get; } |
|||
protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; } |
|||
protected AbpHttpClientOptions ClientOptions { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator { get; } |
|||
|
|||
public HttpProxyExecuter( |
|||
ICancellationTokenProvider cancellationTokenProvider, |
|||
ICorrelationIdProvider correlationIdProvider, |
|||
ICurrentTenant currentTenant, |
|||
IOptions<AbpCorrelationIdOptions> abpCorrelationIdOptions, |
|||
IProxyHttpClientFactory httpClientFactory, |
|||
IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider, |
|||
IOptions<AbpHttpClientOptions> clientOptions, |
|||
IRemoteServiceHttpClientAuthenticator clientAuthenticator, |
|||
IJsonSerializer jsonSerializer) |
|||
{ |
|||
CancellationTokenProvider = cancellationTokenProvider; |
|||
CorrelationIdProvider = correlationIdProvider; |
|||
CurrentTenant = currentTenant; |
|||
AbpCorrelationIdOptions = abpCorrelationIdOptions.Value; |
|||
HttpClientFactory = httpClientFactory; |
|||
RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider; |
|||
ClientOptions = clientOptions.Value; |
|||
ClientAuthenticator = clientAuthenticator; |
|||
JsonSerializer = jsonSerializer; |
|||
} |
|||
|
|||
public virtual async Task<T> MakeRequestAndGetResultAsync<T>(HttpProxyExecuterContext context) |
|||
{ |
|||
var responseContent = await MakeRequestAsync(context); |
|||
|
|||
if (typeof(T) == typeof(IRemoteStreamContent) || |
|||
typeof(T) == typeof(RemoteStreamContent)) |
|||
{ |
|||
/* returning a class that holds a reference to response |
|||
* content just to be sure that GC does not dispose of |
|||
* it before we finish doing our work with the stream */ |
|||
return (T) (object) new RemoteStreamContent( |
|||
await responseContent.ReadAsStreamAsync(), |
|||
responseContent.Headers?.ContentDisposition?.FileNameStar ?? RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), |
|||
responseContent.Headers?.ContentType?.ToString(), |
|||
responseContent.Headers?.ContentLength); |
|||
} |
|||
|
|||
var stringContent = await responseContent.ReadAsStringAsync(); |
|||
if (typeof(T) == typeof(string)) |
|||
{ |
|||
return (T)(object)stringContent; |
|||
} |
|||
|
|||
if (stringContent.IsNullOrWhiteSpace()) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
return JsonSerializer.Deserialize<T>(stringContent); |
|||
} |
|||
|
|||
public virtual async Task<HttpContent> MakeRequestAsync(HttpProxyExecuterContext context) |
|||
{ |
|||
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(context.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {context.ServiceType.FullName}."); |
|||
var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); |
|||
|
|||
var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); |
|||
|
|||
var apiVersion = await GetApiVersionInfoAsync(context); |
|||
var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + UrlBuilder.GenerateUrlWithParameters(context.Action, context.Arguments, apiVersion); |
|||
|
|||
var requestMessage = new HttpRequestMessage(context.Action.GetHttpMethod(), url) |
|||
{ |
|||
Content = RequestPayloadBuilder.BuildContent(context.Action, context.Arguments, JsonSerializer, apiVersion) |
|||
}; |
|||
|
|||
AddHeaders(context.Arguments, context.Action, requestMessage, apiVersion); |
|||
|
|||
if (context.Action.AllowAnonymous != true) |
|||
{ |
|||
await ClientAuthenticator.Authenticate( |
|||
new RemoteServiceHttpClientAuthenticateContext( |
|||
client, |
|||
requestMessage, |
|||
remoteServiceConfig, |
|||
clientConfig.RemoteServiceName |
|||
) |
|||
); |
|||
} |
|||
|
|||
var response = await client.SendAsync( |
|||
requestMessage, |
|||
HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/, |
|||
GetCancellationToken(context.Arguments) |
|||
); |
|||
|
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
await ThrowExceptionForResponseAsync(response); |
|||
} |
|||
|
|||
return response.Content; |
|||
} |
|||
|
|||
private async Task<ApiVersionInfo> GetApiVersionInfoAsync(HttpProxyExecuterContext context) |
|||
{ |
|||
var apiVersion = await FindBestApiVersionAsync(context); |
|||
|
|||
//TODO: Make names configurable?
|
|||
var versionParam = context.Action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ?? |
|||
context.Action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query); |
|||
|
|||
return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); |
|||
} |
|||
|
|||
private async Task<string> FindBestApiVersionAsync(HttpProxyExecuterContext context) |
|||
{ |
|||
var configuredVersion = await GetConfiguredApiVersionAsync(context); |
|||
|
|||
if (context.Action.SupportedVersions.IsNullOrEmpty()) |
|||
{ |
|||
return configuredVersion ?? "1.0"; |
|||
} |
|||
|
|||
if (context.Action.SupportedVersions.Contains(configuredVersion)) |
|||
{ |
|||
return configuredVersion; |
|||
} |
|||
|
|||
return context.Action.SupportedVersions.Last(); //TODO: Ensure to get the latest version!
|
|||
} |
|||
|
|||
private async Task<string> GetConfiguredApiVersionAsync(HttpProxyExecuterContext context) |
|||
{ |
|||
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(context.ServiceType) |
|||
?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {context.ServiceType.FullName}."); |
|||
|
|||
return (await RemoteServiceConfigurationProvider |
|||
.GetConfigurationOrDefaultOrNullAsync(clientConfig.RemoteServiceName))?.Version; |
|||
} |
|||
|
|||
private async Task ThrowExceptionForResponseAsync(HttpResponseMessage response) |
|||
{ |
|||
if (response.Headers.Contains(AbpHttpConsts.AbpErrorFormat)) |
|||
{ |
|||
var errorResponse = JsonSerializer.Deserialize<RemoteServiceErrorResponse>( |
|||
await response.Content.ReadAsStringAsync() |
|||
); |
|||
|
|||
throw new AbpRemoteCallException(errorResponse.Error) |
|||
{ |
|||
HttpStatusCode = (int) response.StatusCode |
|||
}; |
|||
} |
|||
|
|||
throw new AbpRemoteCallException( |
|||
new RemoteServiceErrorInfo |
|||
{ |
|||
Message = response.ReasonPhrase, |
|||
Code = response.StatusCode.ToString() |
|||
} |
|||
) |
|||
{ |
|||
HttpStatusCode = (int) response.StatusCode |
|||
}; |
|||
} |
|||
|
|||
protected virtual void AddHeaders( |
|||
IReadOnlyDictionary<string, object> argumentsDictionary, |
|||
ActionApiDescriptionModel action, |
|||
HttpRequestMessage requestMessage, |
|||
ApiVersionInfo apiVersion) |
|||
{ |
|||
//API Version
|
|||
if (!apiVersion.Version.IsNullOrEmpty()) |
|||
{ |
|||
//TODO: What about other media types?
|
|||
requestMessage.Headers.Add("accept", $"{MimeTypes.Text.Plain}; v={apiVersion.Version}"); |
|||
requestMessage.Headers.Add("accept", $"{MimeTypes.Application.Json}; v={apiVersion.Version}"); |
|||
requestMessage.Headers.Add("api-version", apiVersion.Version); |
|||
} |
|||
|
|||
//Header parameters
|
|||
var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray(); |
|||
foreach (var headerParameter in headers) |
|||
{ |
|||
var value = HttpActionParameterHelper.FindParameterValue(argumentsDictionary, headerParameter); |
|||
if (value != null) |
|||
{ |
|||
requestMessage.Headers.Add(headerParameter.Name, value.ToString()); |
|||
} |
|||
} |
|||
|
|||
//CorrelationId
|
|||
requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get()); |
|||
|
|||
//TenantId
|
|||
if (CurrentTenant.Id.HasValue) |
|||
{ |
|||
//TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key
|
|||
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); |
|||
} |
|||
|
|||
//Culture
|
|||
//TODO: Is that the way we want? Couldn't send the culture (not ui culture)
|
|||
var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; |
|||
if (!currentCulture.IsNullOrEmpty()) |
|||
{ |
|||
requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); |
|||
} |
|||
|
|||
//X-Requested-With
|
|||
requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); |
|||
} |
|||
|
|||
protected virtual StringSegment RemoveQuotes(StringSegment input) |
|||
{ |
|||
if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"') |
|||
{ |
|||
input = input.Subsegment(1, input.Length - 2); |
|||
} |
|||
|
|||
return input; |
|||
} |
|||
|
|||
protected virtual CancellationToken GetCancellationToken(IReadOnlyDictionary<string, object> arguments) |
|||
{ |
|||
var cancellationTokenArg = arguments.LastOrDefault(); |
|||
|
|||
if (cancellationTokenArg.Value is CancellationToken cancellationToken) |
|||
{ |
|||
if (cancellationToken != default) |
|||
{ |
|||
return cancellationToken; |
|||
} |
|||
} |
|||
|
|||
return CancellationTokenProvider.Token; |
|||
} |
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Http.Client.Proxying |
|||
{ |
|||
public interface IHttpProxyExecuter |
|||
{ |
|||
Task<HttpContent> MakeRequestAsync(HttpProxyExecuterContext context); |
|||
|
|||
Task<T> MakeRequestAndGetResultAsync<T>(HttpProxyExecuterContext context); |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,66 +0,0 @@ |
|||
import { AfterViewInit, Directive, ElementRef, Input, Optional, Renderer2 } from '@angular/core'; |
|||
import { Subject } from 'rxjs'; |
|||
|
|||
/** |
|||
* |
|||
* @deprecated To be deleted in v5.0 |
|||
*/ |
|||
@Directive({ |
|||
selector: '[abpVisibility]', |
|||
}) |
|||
export class VisibilityDirective implements AfterViewInit { |
|||
@Input('abpVisibility') |
|||
focusedElement: HTMLElement; |
|||
|
|||
completed$ = new Subject<boolean>(); |
|||
|
|||
constructor(@Optional() private elRef: ElementRef, private renderer: Renderer2) {} |
|||
|
|||
ngAfterViewInit() { |
|||
if (!this.focusedElement && this.elRef) { |
|||
this.focusedElement = this.elRef.nativeElement; |
|||
} |
|||
|
|||
const observer = new MutationObserver(mutations => { |
|||
mutations.forEach(mutation => { |
|||
if (!mutation.target) return; |
|||
|
|||
const htmlNodes = |
|||
Array.from(mutation.target.childNodes || []).filter( |
|||
node => node instanceof HTMLElement, |
|||
) || []; |
|||
|
|||
if (!htmlNodes.length) { |
|||
this.removeFromDOM(); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
observer.observe(this.focusedElement, { |
|||
childList: true, |
|||
}); |
|||
|
|||
setTimeout(() => { |
|||
const htmlNodes = |
|||
Array.from(this.focusedElement.childNodes || []).filter( |
|||
node => node instanceof HTMLElement, |
|||
) || []; |
|||
|
|||
if (!htmlNodes.length) this.removeFromDOM(); |
|||
}, 0); |
|||
|
|||
this.completed$.subscribe(() => observer.disconnect()); |
|||
} |
|||
|
|||
disconnect() { |
|||
this.completed$.next(); |
|||
this.completed$.complete(); |
|||
} |
|||
|
|||
removeFromDOM() { |
|||
if (!this.elRef.nativeElement) return; |
|||
|
|||
this.renderer.removeChild(this.elRef.nativeElement.parentElement, this.elRef.nativeElement); |
|||
this.disconnect(); |
|||
} |
|||
} |
|||
@ -1,117 +0,0 @@ |
|||
import { ABP } from './common'; |
|||
|
|||
export namespace ApplicationConfiguration { |
|||
/** |
|||
* @deprecated Use the ApplicationConfigurationDto interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface Response { |
|||
localization: Localization; |
|||
auth: Auth; |
|||
setting: Value; |
|||
currentUser: CurrentUser; |
|||
currentTenant: CurrentTenant; |
|||
features: Value; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the ApplicationLocalizationConfigurationDto interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface Localization { |
|||
currentCulture: CurrentCulture; |
|||
defaultResourceName: string; |
|||
languages: Language[]; |
|||
values: LocalizationValue; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the Record<string, Record<string, string>> type instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface LocalizationValue { |
|||
[key: string]: { [key: string]: string }; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the LanguageInfo interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface Language { |
|||
cultureName: string; |
|||
uiCultureName: string; |
|||
displayName: string; |
|||
flagIcon: string; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the CurrentCultureDto interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface CurrentCulture { |
|||
cultureName: string; |
|||
dateTimeFormat: DateTimeFormat; |
|||
displayName: string; |
|||
englishName: string; |
|||
isRightToLeft: boolean; |
|||
name: string; |
|||
nativeName: string; |
|||
threeLetterIsoLanguageName: string; |
|||
twoLetterIsoLanguageName: string; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the DateTimeFormatDto interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface DateTimeFormat { |
|||
calendarAlgorithmType: string; |
|||
dateSeparator: string; |
|||
fullDateTimePattern: string; |
|||
longTimePattern: string; |
|||
shortDatePattern: string; |
|||
shortTimePattern: string; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the ApplicationAuthConfigurationDto interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface Auth { |
|||
policies: Policy; |
|||
grantedPolicies: Policy; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the Record<string, boolean> type instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface Policy { |
|||
[key: string]: boolean; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated To be deleted in v5.0. |
|||
*/ |
|||
export interface Value { |
|||
values: ABP.Dictionary<string>; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the CurrentUserDto interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface CurrentUser { |
|||
email: string; |
|||
emailVerified: false; |
|||
id: string; |
|||
isAuthenticated: boolean; |
|||
roles: string[]; |
|||
tenantId: string; |
|||
userName: string; |
|||
name: string; |
|||
phoneNumber: string; |
|||
phoneNumberVerified: boolean; |
|||
surName: string; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use the CurrentTenantDto interface instead. To be deleted in v5.0. |
|||
*/ |
|||
export interface CurrentTenant { |
|||
id: string; |
|||
name: string; |
|||
isAvailable?: boolean; |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
import { Rest } from '../models/rest'; |
|||
import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models'; |
|||
import { RestService } from './rest.service'; |
|||
|
|||
/** |
|||
* @deprecated Use AbpApplicationConfigurationService instead. To be deleted in v5.0. |
|||
*/ |
|||
@Injectable({ |
|||
providedIn: 'root', |
|||
}) |
|||
export class ApplicationConfigurationService { |
|||
constructor(private rest: RestService) {} |
|||
|
|||
getConfiguration(): Observable<ApplicationConfigurationDto> { |
|||
const request: Rest.Request<null> = { |
|||
method: 'GET', |
|||
url: '/api/abp/application-configuration', |
|||
}; |
|||
|
|||
return this.rest.request<null, ApplicationConfigurationDto>(request, {}); |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; |
|||
import { of } from 'rxjs'; |
|||
import { ApplicationConfigurationService, RestService } from '../services'; |
|||
|
|||
describe('ApplicationConfigurationService', () => { |
|||
let spectator: SpectatorService<ApplicationConfigurationService>; |
|||
const createService = createServiceFactory({ |
|||
service: ApplicationConfigurationService, |
|||
mocks: [RestService], |
|||
}); |
|||
|
|||
beforeEach(() => (spectator = createService())); |
|||
|
|||
it('should send a GET to application-configuration API', () => { |
|||
const rest = spectator.inject(RestService); |
|||
|
|||
const requestSpy = jest.spyOn(rest, 'request'); |
|||
requestSpy.mockReturnValue(of(null)); |
|||
|
|||
spectator.service.getConfiguration().subscribe(); |
|||
|
|||
expect(requestSpy).toHaveBeenCalledWith( |
|||
{ method: 'GET', url: '/api/abp/application-configuration' }, |
|||
{}, |
|||
); |
|||
}); |
|||
}); |
|||
@ -1,126 +0,0 @@ |
|||
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; |
|||
import { VisibilityDirective } from '../directives/visibility.directive'; |
|||
|
|||
describe('VisibilityDirective', () => { |
|||
let spectator: SpectatorDirective<VisibilityDirective>; |
|||
let directive: VisibilityDirective; |
|||
const createDirective = createDirectiveFactory({ |
|||
directive: VisibilityDirective, |
|||
}); |
|||
|
|||
describe('without content', () => { |
|||
beforeEach(() => { |
|||
spectator = createDirective('<div [abpVisibility]></div>'); |
|||
directive = spectator.directive; |
|||
}); |
|||
|
|||
it('should be created', () => { |
|||
expect(directive).toBeTruthy(); |
|||
}); |
|||
|
|||
xit('should be removed', done => { |
|||
setTimeout(() => { |
|||
expect(spectator.query('div')).toBeFalsy(); |
|||
done(); |
|||
}, 0); |
|||
}); |
|||
}); |
|||
|
|||
describe('without mutation observer and with content', () => { |
|||
beforeEach(() => { |
|||
spectator = createDirective('<div [abpVisibility]><p id="content">Content</p></div>'); |
|||
directive = spectator.directive; |
|||
}); |
|||
|
|||
it('should not removed', done => { |
|||
setTimeout(() => { |
|||
expect(spectator.query('div')).toBeTruthy(); |
|||
done(); |
|||
}, 0); |
|||
}); |
|||
}); |
|||
|
|||
describe('without mutation observer and with focused element', () => { |
|||
beforeEach(() => { |
|||
spectator = createDirective( |
|||
'<div id="main" [abpVisibility]="container"></div><div #container><p id="content">Content</p></div>', |
|||
); |
|||
directive = spectator.directive; |
|||
}); |
|||
|
|||
it('should not removed', done => { |
|||
setTimeout(() => { |
|||
expect(spectator.query('#main')).toBeTruthy(); |
|||
done(); |
|||
}, 0); |
|||
}); |
|||
}); |
|||
|
|||
describe('without content and with focused element', () => { |
|||
beforeEach(() => { |
|||
spectator = createDirective( |
|||
'<div id="main" [abpVisibility]="container"></div><div #container></div>', |
|||
); |
|||
directive = spectator.directive; |
|||
}); |
|||
|
|||
xit('should be removed', done => { |
|||
setTimeout(() => { |
|||
expect(spectator.query('#main')).toBeFalsy(); |
|||
done(); |
|||
}, 0); |
|||
}); |
|||
}); |
|||
|
|||
describe('with mutation observer and with content', () => { |
|||
beforeEach(() => { |
|||
spectator = createDirective('<div [abpVisibility]><div id="content">Content</div></div>'); |
|||
directive = spectator.directive; |
|||
}); |
|||
|
|||
xit('should remove the main div element when content removed', done => { |
|||
spectator.query('#content').remove(); |
|||
|
|||
setTimeout(() => { |
|||
expect(spectator.query('div')).toBeFalsy(); |
|||
done(); |
|||
}, 0); |
|||
}); |
|||
|
|||
it('should not remove the main div element', done => { |
|||
spectator.query('div').appendChild(document.createElement('div')); |
|||
|
|||
setTimeout(() => { |
|||
expect(spectator.query('div')).toBeTruthy(); |
|||
done(); |
|||
}, 100); |
|||
}); |
|||
}); |
|||
|
|||
describe('with mutation observer and with focused element', () => { |
|||
beforeEach(() => { |
|||
spectator = createDirective( |
|||
'<div id="main" [abpVisibility]="container"></div><div #container><p id="content">Content</p></div>', |
|||
); |
|||
directive = spectator.directive; |
|||
}); |
|||
|
|||
xit('should remove the main div element when content removed', done => { |
|||
spectator.query('#content').remove(); |
|||
|
|||
setTimeout(() => { |
|||
expect(spectator.query('#main')).toBeFalsy(); |
|||
done(); |
|||
}, 0); |
|||
}); |
|||
|
|||
it('should not remove the main div element', done => { |
|||
spectator.query('#content').appendChild(document.createElement('div')); |
|||
|
|||
setTimeout(() => { |
|||
expect(spectator.query('#main')).toBeTruthy(); |
|||
done(); |
|||
}, 100); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -1,31 +0,0 @@ |
|||
import { Observable, Subject } from 'rxjs'; |
|||
import { takeUntil } from 'rxjs/operators'; |
|||
|
|||
function isFunction(value) { |
|||
return typeof value === 'function'; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated no longer working, please use SubscriptionService (https://docs.abp.io/en/abp/latest/UI/Angular/Subscription-Service) instead.
|
|||
*/ |
|||
export const takeUntilDestroy = |
|||
(componentInstance, destroyMethodName = 'ngOnDestroy') => |
|||
<T>(source: Observable<T>) => { |
|||
const originalDestroy = componentInstance[destroyMethodName]; |
|||
if (isFunction(originalDestroy) === false) { |
|||
throw new Error( |
|||
`${componentInstance.constructor.name} is using untilDestroyed but doesn't implement ${destroyMethodName}`, |
|||
); |
|||
} |
|||
if (!componentInstance['__takeUntilDestroy']) { |
|||
componentInstance['__takeUntilDestroy'] = new Subject(); |
|||
|
|||
componentInstance[destroyMethodName] = function () { |
|||
// eslint-disable-next-line prefer-rest-params
|
|||
isFunction(originalDestroy) && originalDestroy.apply(this, arguments); |
|||
componentInstance['__takeUntilDestroy'].next(true); |
|||
componentInstance['__takeUntilDestroy'].complete(); |
|||
}; |
|||
} |
|||
return source.pipe(takeUntil<T>(componentInstance['__takeUntilDestroy'])); |
|||
}; |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue