Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into feat/theme-shared/custom-error

pull/2142/head
mehmet-erim 7 years ago
parent
commit
8985187fe8
  1. 3
      docs/en/Configuration.md
  2. 3
      docs/en/CurrentUser.md
  3. 3
      docs/en/Localization.md
  4. 2
      docs/en/Modules/Index.md
  5. 83
      docs/en/Modules/Setting-Management.md
  6. 4
      docs/en/Options.md
  7. 177
      docs/en/Settings.md
  8. 4
      docs/en/String-Encryption.md
  9. 8
      docs/en/Tutorials/Angular/Part-II.md
  10. 13
      docs/en/Virtual-File-System.md
  11. 11
      docs/en/docs-nav.json
  12. 3
      docs/zh-Hans/Localization.md
  13. 12
      docs/zh-Hans/Virtual-File-System.md
  14. 2
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Edit.cshtml
  15. 14
      npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts
  16. 16
      npm/ng-packs/packages/core/src/lib/directives/ellipsis.directive.ts
  17. 10
      npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts
  18. 36
      npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts
  19. 85
      npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts
  20. 40
      npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts
  21. 55
      npm/ng-packs/packages/core/src/lib/tests/ellipsis.directive.spec.ts
  22. 39
      npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts
  23. 1
      npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts

3
docs/en/Configuration.md

@ -0,0 +1,3 @@
# Configuration
ASP.NET Core has an flexible and extensible key-value based configuration system. In fact, the configuration system is a part of Microsoft.Extensions libraries and it is independent from ASP.NET Core. That means it can be used in any type of application. See [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) to learn the configuration infrastructure. ABP framework is 100% compatible with the configuration system.

3
docs/en/CurrentUser.md

@ -0,0 +1,3 @@
# Current User
TODO!

3
docs/en/Localization.md

@ -48,7 +48,8 @@ public class MyModule : AbpModule
{
Configure<VirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>();
// "YourRootNameSpace" is the root namespace of your project. It can be empty if your root namespace is empty.
options.FileSets.AddEmbedded<MyModule>("YourRootNameSpace");
});
Configure<AbpLocalizationOptions>(options =>

2
docs/en/Modules/Index.md

@ -19,7 +19,7 @@ There are some **free and open source** application modules developed and mainta
* **Identity**: Used to manage roles, users and their permissions.
* **Identity Server**: Integrates to IdentityServer4.
* **Permission Management**: Used to persist permissions.
* **Setting Management**: Used to persist settings.
* **[Setting Management](Setting-Management.md)**: Used to persist and manage the [settings](../Settings.md).
* **Tenant Management**: Used to manage tenants for a [multi-tenant](../Multi-Tenancy.md) application.
* **Users**: Used to abstract users, so other modules can depend on this instead of the Identity module.

83
docs/en/Modules/Setting-Management.md

@ -0,0 +1,83 @@
# Setting Management Module
Setting Management Module implements the `ISettingStore` (see [the setting system](../Settings.md)) to store the setting values in a database and provides the `ISettingManager` to manage (change) the setting values in the database.
> Setting Management module is already installed and configured for [the startup templates](../Startup-Templates/Index.md). So, most of the times you don't need to manually add this module to your application.
## ISettingManager
`ISettingManager` is used to get and set the values for the settings. Examples:
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.SettingManagement;
namespace Demo
{
public class MyService : ITransientDependency
{
private readonly ISettingManager _settingManager;
//Inject ISettingManager service
public MyService(ISettingManager settingManager)
{
_settingManager = settingManager;
}
public async Task FooAsync()
{
Guid user1Id = ...;
Guid tenant1Id = ...;
//Get/set a setting value for the current user or the specified user
string layoutType1 =
await _settingManager.GetOrNullForCurrentUserAsync("App.UI.LayoutType");
string layoutType2 =
await _settingManager.GetOrNullForUserAsync("App.UI.LayoutType", user1Id);
await _settingManager.SetForCurrentUserAsync("App.UI.LayoutType", "LeftMenu");
await _settingManager.SetForUserAsync(user1Id, "App.UI.LayoutType", "LeftMenu");
//Get/set a setting value for the current tenant or the specified tenant
string layoutType3 =
await _settingManager.GetOrNullForCurrentTenantAsync("App.UI.LayoutType");
string layoutType4 =
await _settingManager.GetOrNullForTenantAsync("App.UI.LayoutType", tenant1Id);
await _settingManager.SetForCurrentTenantAsync("App.UI.LayoutType", "LeftMenu");
await _settingManager.SetForTenantAsync(tenant1Id, "App.UI.LayoutType", "LeftMenu");
//Get/set a global and default setting value
string layoutType5 =
await _settingManager.GetOrNullGlobalAsync("App.UI.LayoutType");
string layoutType6 =
await _settingManager.GetOrNullDefaultAsync("App.UI.LayoutType");
await _settingManager.SetGlobalAsync("App.UI.LayoutType", "TopMenu");
}
}
}
````
So, you can get or set a setting value for different setting value providers (Default, Global, User, Tenant... etc).
### Setting Cache
Setting values are cached using the [distributed cache](../Caching.md) system. Always use the `ISettingManager` to change the setting values which manages the cache for you.
## Setting Management Providers
Setting Management module is extensible, just like the [setting system](../Settings.md). You can extend it by defining setting management providers. There are four pre-built setting management providers registered by the order below:
* `DefaultValueSettingManagementProvider`: Gets the value from the default value of the setting definition. It can not set the default value since default values are hard-coded on the setting definition.
* `GlobalSettingManagementProvider`: Gets or sets the global (system-wide) value for a setting.
* `TenantSettingManagementProvider`: Gets or sets the setting value for a tenant.
* `UserSettingManagementProvider`: Gets the setting value for a user.
`ISettingManager` uses the setting management providers on get/set methods. Typically, every setting management provider defines extension methods on the `ISettingManagement` service (like `SetForUserAsync` defined by the user setting management provider).

4
docs/en/Options.md

@ -0,0 +1,4 @@
# Options
TODO!

177
docs/en/Settings.md

@ -1,3 +1,178 @@
# Settings
TODO!
[Configuration system](Configuration.md) is a good way to configure the application on startup. In addition to the configurations, ABP provides another way to set and get some application settings.
A setting is a name-value pair stored in a dynamic data source, generally in a database. Setting system is extensible and there are pre-built provides for a user, a tenant, global and default.
## Defining Settings
A setting must be defined before its use. ABP was designed to be [modular](Module-Development-Basics.md), so different modules can have different settings. A module must create a class derived from the `SettingDefinitionProvider` in order to define its settings. An example setting definition provider is shown below:
````csharp
public class EmailSettingProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition("Smtp.Host", "127.0.0.1"),
new SettingDefinition("Smtp.Port", "25"),
new SettingDefinition("Smtp.UserName"),
new SettingDefinition("Smtp.Password", isEncrypted: true),
new SettingDefinition("Smtp.EnableSsl", "false")
);
}
}
````
ABP automatically discovers this class and registers the setting definitions.
### SettingDefinition
`SettingDefinition` class has the following properties:
* **Name**: Unique name of the setting in the application. This is **the only mandatory property**. Used to get/set the value of this setting in the application code (It's a good idea to define a const string for a setting name instead of using a magic string).
* **DefaultValue**: A setting may have a default value.
* **DisplayName**: A localizable string that can be used to show the setting name on the UI.
* **Description**: A localizable string that can be used to show the setting description on the UI.
* **IsVisibleToClients**: A boolean value indicates that whether this setting value is available in the client side or not. Default value is false to prevent accidently publishing an internal critical setting value.
* **IsInherited**: A boolean value indicates that whether this setting value is inherited from other providers or not. Default value is true and fallbacks to the next provider if the setting value was not set for the requested provider (see the setting value providers section for more).
* **IsEncrypted**: A boolean value indicates that whether this setting value should be encrypted on save and decrypted on read. It makes possible to secure the setting value in the database.
* **Providers**: Can be used to restrict providers available for a particular setting (see the setting value providers section for more).
* **Properties**: A name/value collection to set custom properties about this setting those can be used later in the application code.
## Reading Setting Values
### ISettingProvider
`ISettingProvider` is used to get the value of a setting or get the values of all the settings. Example usages:
````csharp
public class MyService
{
private readonly ISettingProvider _settingProvider;
//Inject ISettingProvider in the constructor
public MyService(ISettingProvider settingProvider)
{
_settingProvider = settingProvider;
}
public async Task FooAsync()
{
//Get a value as string.
string userName = await _settingProvider.GetOrNullAsync("Smtp.UserName");
//Get a bool value and fallback to the default value (false) if not set.
bool enableSsl = await _settingProvider.GetAsync<bool>("Smtp.EnableSsl");
//Get a bool value and fallback to the provided default value (true) if not set.
bool enableSsl = await _settingProvider.GetAsync<bool>(
"Smtp.EnableSsl", defaultValue: true);
//Get a bool value with the IsTrueAsync shortcut extension method
bool enableSsl = await _settingProvider.IsTrueAsync("Smtp.EnableSsl");
//Get an int value or the default value (0) if not set
int port = (await _settingProvider.GetAsync<int>("Smtp.Port"));
//Get an int value or null if not provided
int? port = (await _settingProvider.GetOrNullAsync("Smtp.Port"))?.To<int>();
}
}
````
> `ISettingProvider` is a very common service and some base classes (like `IApplicationService`) already property-inject it. You can directly use the `SettingProvider` in such cases.
### Reading Setting Values on the Client Side
If a setting is allowed to be visible on the client side, current value of the setting can also be read from the JavaScript code. Examples:
````js
//Gets a value as string.
var language = abp.setting.get('Abp.Localization.DefaultLanguage');
//Gets an integer value.
var requiredLength = abp.setting.getInt('Abp.Identity.Password.RequiredLength');
//Gets a boolean value.
var requireDigit = abp.setting.getBoolean('Abp.Identity.Password.RequireDigit');
````
In addition, use `abp.setting.values` to get a dictionary of all the setting values.
## Setting Value Providers
Setting system is extensible, you can extend it by defining setting value providers to get setting values from any source and based on any condition.
`ISettingProvider` uses the setting value providers to obtain a setting value. It fallbacks to the next value provider if a value provider can not get the setting value.
There are four pre-built setting value providers registered by the order below:
* `DefaultValueSettingValueProvider`: Gets the value from the default value of the setting definition, if set (see the SettingDefinition section above).
* `GlobalSettingValueProvider`: Gets the global (system-wide) value for a setting, if set.
* `TenantSettingValueProvider`: Gets the setting value for the current tenant, if set (see the [multi-tenancy](Multi-Tenancy.md) document).
* `UserSettingValueProvider`: Gets the setting value for the current user, if set (see the [current user](CurrentUser.md) document).
> Setting fallback system works from bottom (user) to top (default).
Global, Tenant and User setting value providers use the `ISettingStore` to read the value from the data source (see the section below).
### Custom Setting Value Providers
If you need to extend the setting system, you can define a class derived from the `SettingValueProvider` class. Example:
````csharp
public class CustomSettingValueProvider : SettingValueProvider
{
public override string Name => "Custom";
public CustomSettingValueProvider(ISettingStore settingStore)
: base(settingStore)
{
}
public override Task<string> GetOrNullAsync(SettingDefinition setting)
{
/* Return the setting value or null
Use the SettingStore or another data source */
}
}
````
> Alternatively, you can implement the `ISettingValueProvider` interface. Remember to register it to the [dependency injection](Dependency-Injection.md) in this case.
Every provider should have a unique Name (which is "Custom" here). Built-in providers use the given names:
* `DefaultValueSettingValueProvider`: "**D**".
* `GlobalSettingValueProvider`: "**G**".
* `TenantSettingValueProvider`: "**T**".
* `UserSettingValueProvider`: "**U**".
One-letter names were preferred to reduce the data size in the database (provider name is repeated in each row).
Once you define a custom setting value provider, you need to explicitly register it to the `AbpSettingOptions`:
````csharp
Configure<AbpSettingOptions>(options =>
{
options.ValueProviders.Add<CustomSettingValueProvider>();
});
````
This example adds it as the last item, so it will be the first value provider used by the `ISettingProvider`. You could add it to another index in the `options.ValueProviders` list.
### ISettingStore
While a setting value provider is free to use any source to get the setting value, the`ISettingStore` service is the default source of the setting values. Global, Tenant and User setting value providers use it.
## ISettingEncryptionService
`ISettingEncryptionService` is used to encrypt/decrypt setting values when `IsEncrypted` property of a setting definition was set to `true`.
You can replace this service in the dependency injection system to customize the encryption/decryption process. Default implementation uses the `IStringEncryptionService` which is implemented with the AES algorithm by default (see string [encryption document](String-Encryption.md) for more).
## Setting Management Module
The core setting system is pretty independent and doesn't make any assumption about how you manage (change) the setting values. Even the default `ISettingStore` implementation is the `NullSettingStore` which returns null for all setting values.
The setting management module completes it (and implements `ISettingStore`) by managing setting values in a database. See the [Setting Management Module document](Modules/Setting-Management.md) for more.

4
docs/en/String-Encryption.md

@ -0,0 +1,4 @@
# String Encryption
TODO!

8
docs/en/Tutorials/Angular/Part-II.md

@ -137,7 +137,7 @@ createBook() {
Add a `form` variable and inject a `FormBuilder` service to the `book-list.component.ts` as shown below (remember add the import statement).
```js
import { FormGroup, FormBuilder } from '@angular/forms';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
form: FormGroup;
@ -222,12 +222,13 @@ Open the `book-list.component.ts` and then create an array, named `bookTypeArr`:
```js
//...
form: FormGroup;
booksType = Books.BookType;
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
```
> It use the `booksType` field,please put the `bookTypeArr` defined below that line
The `bookTypeArr` contains the fields of the `BookType` enum. Resulting array is shown below:
@ -293,6 +294,9 @@ This adds a save button to the bottom area of the modal:
Then define a `save` method in the `BookListComponent`:
```js
//...
import { ..., CreateUpdateBook } from '../../store/actions';
//...
save() {
if (this.form.invalid) {
return;

13
docs/en/Virtual-File-System.md

@ -44,7 +44,7 @@ If you want to add multiple files, this can be tedious. Alternatively, you can d
This configuration recursively adds all files under the **MyResources** folder of the project (including the files you will add in the future).
Then the module needs to be configured using `VirtualFileSystemOptions` to register the embedded files to the virtual file system. Example:
Then the module needs to be configured using `AbpVirtualFileSystemOptions` to register the embedded files to the virtual file system. Example:
````C#
using Microsoft.Extensions.DependencyInjection;
@ -58,10 +58,10 @@ namespace MyCompany.MyProject
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<VirtualFileSystemOptions>(options =>
Configure<AbpVirtualFileSystemOptions>(options =>
{
//Register all embedded files of this assembly to the virtual file system
options.FileSets.AddEmbedded<MyModule>();
options.FileSets.AddEmbedded<MyModule>("YourRootNameSpace");
});
//...
@ -73,9 +73,12 @@ namespace MyCompany.MyProject
The `AddEmbedded` extension method takes a class, finds all embedded files from the assembly of the given class and registers them to the virtual file system. More concisely it could be written as follows:
````C#
options.FileSets.Add(new EmbeddedFileSet(typeof(MyModule).Assembly));
options.FileSets.Add(
new EmbeddedFileSet(typeof(MyModule).Assembly), "YourRootNameSpace");
````
> "YourRootNameSpace" is the root namespace of your project. It can be empty if your root namespace is empty.
#### Getting Virtual Files: IVirtualFileProvider
After embedding a file into an assembly and registering it to the virtual file system, the `IVirtualFileProvider` interface can be used to get files or directory contents:
@ -122,7 +125,7 @@ public class MyWebAppModule : AbpModule
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<VirtualFileSystemOptions>(options =>
Configure<AbpVirtualFileSystemOptions>(options =>
{
//ReplaceEmbeddedByPhysical gets the root folder of the MyModule project
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(

11
docs/en/docs-nav.json

@ -56,6 +56,10 @@
{
"text": "Fundamentals",
"items": [
{
"text": "Configuration",
"path": "Configuration.md"
},
{
"text": "Dependency Injection",
"path": "Dependency-Injection.md",
@ -93,7 +97,8 @@
"text": "Auditing"
},
{
"text": "Setting Management"
"text": "Settings",
"path": "Settings.md"
}
]
},
@ -116,6 +121,10 @@
{
"text": "Services",
"items": [
{
"text": "Current User",
"path": "CurrentUser.md"
},
{
"text": "Object to object mapping",
"path": "Object-To-Object-Mapping.md"

3
docs/zh-Hans/Localization.md

@ -48,7 +48,8 @@ public class MyModule : AbpModule
{
Configure<VirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>();
// "YourRootNameSpace" 是项目的根命名空间名字. 如果你的项目的根命名空间名字为空,则无需传递此参数.
options.FileSets.AddEmbedded<MyModule>("YourRootNameSpace");
});
Configure<AbpLocalizationOptions>(options =>

12
docs/zh-Hans/Virtual-File-System.md

@ -45,7 +45,7 @@ namespace MyCompany.MyProject
此配置以递归方式添加项目的 **MyResources** 文件夹下的所有文件(包括将来新添加的文件).
然后需要使用 `VirtualFileSystemOptions` 来配置模块, 以便将嵌入式文件注册到虚拟文件系统. 例如:
然后需要使用 `AbpVirtualFileSystemOptions` 来配置模块, 以便将嵌入式文件注册到虚拟文件系统. 例如:
````C#
using Microsoft.Extensions.DependencyInjection;
@ -59,10 +59,10 @@ namespace MyCompany.MyProject
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<VirtualFileSystemOptions>(options =>
Configure<AbpVirtualFileSystemOptions>(options =>
{
//Register all embedded files of this assembly to the virtual file system
options.FileSets.AddEmbedded<MyModule>();
options.FileSets.AddEmbedded<MyModule>("YourRootNameSpace");
});
//...
@ -74,9 +74,11 @@ namespace MyCompany.MyProject
`AddEmbedded` 扩展方法需要一个类, 从给定类的程序集中查找所有嵌入文件, 并将它们注册到虚拟文件系统. 它还有更简洁的写法:
````C#
options.FileSets.Add(new EmbeddedFileSet(typeof(MyModule).Assembly));
options.FileSets.Add(new EmbeddedFileSet(typeof(MyModule).Assembly), "YourRootNameSpace");
````
> "YourRootNameSpace" 是项目的根命名空间名字. 如果你的项目的根命名空间名字为空,则无需传递此参数.
#### 获取虚拟文件: IVirtualFileProvider
将文件嵌入到程序集中并注册到虚拟文件系统后, 可以使用`IVirtualFileProvider`来获取文件或目录内容:
@ -123,7 +125,7 @@ public class MyWebAppModule : AbpModule
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<VirtualFileSystemOptions>(options =>
Configure<AbpVirtualFileSystemOptions>(options =>
{
//ReplaceEmbeddedByPhysical gets the root folder of the MyModule project
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(

2
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Edit.cshtml

@ -7,7 +7,7 @@
ViewBag.PageTitle = "Blog";
}
@section styles {
<abp-style-bundle @*name="@typeof(EditModel).FullName" *@>
<abp-style-bundle name="@typeof(EditModel).FullName">
<abp-style type="@typeof(TuiEditorStyleContributor)" />
<abp-style src="/Pages/Blog/Posts/new.css" />
</abp-style-bundle>

14
npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts

@ -1,27 +1,29 @@
import { Directive, Output, Renderer2, ElementRef, OnInit, EventEmitter, Input } from '@angular/core';
import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { takeUntilDestroy } from '@ngx-validate/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { takeUntilDestroy } from '@ngx-validate/core';
@Directive({
// tslint:disable-next-line: directive-selector
selector: '[input.debounce]'
selector: '[input.debounce]',
})
export class InputEventDebounceDirective implements OnInit {
export class InputEventDebounceDirective implements OnInit, OnDestroy {
@Input() debounce = 300;
@Output('input.debounce') readonly debounceEvent = new EventEmitter<Event>();
constructor(private renderer: Renderer2, private el: ElementRef) {}
constructor(private el: ElementRef) {}
ngOnInit(): void {
fromEvent(this.el.nativeElement, 'input')
.pipe(
debounceTime(this.debounce),
takeUntilDestroy(this)
takeUntilDestroy(this),
)
.subscribe((event: Event) => {
this.debounceEvent.emit(event);
});
}
ngOnDestroy(): void {}
}

16
npm/ng-packs/packages/core/src/lib/directives/ellipsis.directive.ts

@ -1,9 +1,9 @@
import { AfterContentInit, ChangeDetectorRef, Directive, ElementRef, HostBinding, Input } from '@angular/core';
import { AfterViewInit, ChangeDetectorRef, Directive, ElementRef, HostBinding, Input } from '@angular/core';
@Directive({
selector: '[abpEllipsis]',
})
export class EllipsisDirective implements AfterContentInit {
export class EllipsisDirective implements AfterViewInit {
@Input('abpEllipsis')
width: string;
@ -31,14 +31,8 @@ export class EllipsisDirective implements AfterContentInit {
constructor(private cdRef: ChangeDetectorRef, private elRef: ElementRef) {}
ngAfterContentInit() {
setTimeout(() => {
const title = this.title;
this.title = title || (this.elRef.nativeElement as HTMLElement).innerText;
if (this.title !== title) {
this.cdRef.detectChanges();
}
}, 0);
ngAfterViewInit() {
this.title = this.title || (this.elRef.nativeElement as HTMLElement).innerText;
this.cdRef.detectChanges();
}
}

10
npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts

@ -1,15 +1,15 @@
import { Directive, ElementRef, EventEmitter, OnInit, Output, Renderer2 } from '@angular/core';
import { Directive, ElementRef, EventEmitter, OnInit, Output, Renderer2, OnDestroy } from '@angular/core';
import { fromEvent } from 'rxjs';
import { takeUntilDestroy } from '@ngx-validate/core';
@Directive({
// tslint:disable-next-line: directive-selector
selector: '[click.stop]'
selector: '[click.stop]',
})
export class ClickEventStopPropagationDirective implements OnInit {
export class ClickEventStopPropagationDirective implements OnInit, OnDestroy {
@Output('click.stop') readonly stopPropEvent = new EventEmitter<MouseEvent>();
constructor(private renderer: Renderer2, private el: ElementRef) {}
constructor(private el: ElementRef) {}
ngOnInit(): void {
fromEvent(this.el.nativeElement, 'click')
@ -19,4 +19,6 @@ export class ClickEventStopPropagationDirective implements OnInit {
this.stopPropEvent.emit(event);
});
}
ngOnDestroy(): void {}
}

36
npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts

@ -0,0 +1,36 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { AutofocusDirective } from '../directives/autofocus.directive';
import { timer } from 'rxjs';
describe('AutofocusDirective', () => {
let spectator: SpectatorDirective<AutofocusDirective>;
let directive: AutofocusDirective;
let input: HTMLInputElement;
const createDirective = createDirectiveFactory({
directive: AutofocusDirective,
});
beforeEach(() => {
spectator = createDirective('<input [autofocus]="10" />', {
hostProps: {},
});
directive = spectator.directive;
input = spectator.query('input');
});
test('should be created', () => {
expect(directive).toBeTruthy();
});
test('should have 10ms delay', () => {
expect(directive.delay).toBe(10);
});
test('should focus element after given delay', done => {
timer(0).subscribe(() => expect('input').not.toBeFocused());
timer(11).subscribe(() => {
expect('input').toBeFocused();
done();
});
});
});

85
npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts

@ -2,7 +2,7 @@ import { RouterTestingModule } from '@angular/router/testing';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { NgxsModule, NGXS_PLUGINS, Store } from '@ngxs/store';
import { environment } from '../../../../../apps/dev-app/src/environments/environment';
import { LAYOUTS } from '../../../../theme-basic/src/public-api';
import { LAYOUTS } from '@abp/ng.theme.basic';
import { RouterOutletComponent } from '../components';
import { CoreModule } from '../core.module';
import { eLayoutType } from '../enums/common';
@ -68,27 +68,7 @@ const expectedState = {
path: '',
children: [],
url: '/',
},
{
name: 'AbpAccount::Menu:Account',
path: 'account',
invisible: true,
layout: 'application',
children: [
{
path: 'login',
name: 'AbpAccount::Login',
order: 1,
url: '/account/login',
},
{
path: 'register',
name: 'AbpAccount::Register',
order: 2,
url: '/account/register',
},
],
url: '/account',
order: 1,
},
{
name: 'AbpUiNavigation::Menu:Administration',
@ -137,17 +117,10 @@ const expectedState = {
},
],
url: '/tenant-management',
order: 2,
},
],
},
],
flattedRoutes: [
{
name: '::Menu:Home',
path: '',
children: [],
url: '/',
},
{
name: 'AbpAccount::Menu:Account',
path: 'account',
@ -168,18 +141,16 @@ const expectedState = {
},
],
url: '/account',
order: 2,
},
],
flattedRoutes: [
{
path: 'login',
name: 'AbpAccount::Login',
name: '::Menu:Home',
path: '',
children: [],
url: '/',
order: 1,
url: '/account/login',
},
{
path: 'register',
name: 'AbpAccount::Register',
order: 2,
url: '/account/register',
},
{
name: 'AbpUiNavigation::Menu:Administration',
@ -228,6 +199,7 @@ const expectedState = {
},
],
url: '/tenant-management',
order: 2,
},
],
},
@ -286,6 +258,7 @@ const expectedState = {
},
],
url: '/tenant-management',
order: 2,
},
{
path: 'tenants',
@ -294,6 +267,40 @@ const expectedState = {
requiredPolicy: 'AbpTenantManagement.Tenants',
url: '/tenant-management/tenants',
},
{
name: 'AbpAccount::Menu:Account',
path: 'account',
invisible: true,
layout: 'application',
children: [
{
path: 'login',
name: 'AbpAccount::Login',
order: 1,
url: '/account/login',
},
{
path: 'register',
name: 'AbpAccount::Register',
order: 2,
url: '/account/register',
},
],
url: '/account',
order: 2,
},
{
path: 'login',
name: 'AbpAccount::Login',
order: 1,
url: '/account/login',
},
{
path: 'register',
name: 'AbpAccount::Register',
order: 2,
url: '/account/register',
},
],
};

40
npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts

@ -0,0 +1,40 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { InputEventDebounceDirective } from '../directives/debounce.directive';
import { timer } from 'rxjs';
describe('InputEventDebounceDirective', () => {
let spectator: SpectatorDirective<InputEventDebounceDirective>;
let directive: InputEventDebounceDirective;
let input: HTMLInputElement;
let inputEventFn = jest.fn(() => {});
const createDirective = createDirectiveFactory({
directive: InputEventDebounceDirective,
});
beforeEach(() => {
spectator = createDirective('<input (input.debounce)="inputEventFn()" [debounce]="20" />', {
hostProps: { inputEventFn },
});
directive = spectator.directive;
input = spectator.query('input');
inputEventFn.mockClear();
});
test('should be created', () => {
expect(directive).toBeTruthy();
});
test('should have 20ms debounce time', () => {
expect(directive.debounce).toBe(20);
});
test('should call fromEvent with target element and target event', done => {
spectator.dispatchFakeEvent('input', 'input', true);
timer(0).subscribe(() => expect(inputEventFn).not.toHaveBeenCalled());
timer(21).subscribe(() => {
expect(inputEventFn).toHaveBeenCalled();
done();
});
});
});

55
npm/ng-packs/packages/core/src/lib/tests/ellipsis.directive.spec.ts

@ -0,0 +1,55 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { EllipsisDirective } from '../directives/ellipsis.directive';
describe('EllipsisDirective', () => {
let spectator: SpectatorDirective<EllipsisDirective>;
let directive: EllipsisDirective;
let el: HTMLDivElement;
const createDirective = createDirectiveFactory({
directive: EllipsisDirective,
});
beforeEach(() => {
spectator = createDirective(
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>',
{
hostProps: {
title: 'test title',
width: '100px',
},
},
);
directive = spectator.directive;
el = spectator.query('div');
});
test('should be created', () => {
expect(directive).toBeTruthy();
});
test('should have 100px ellipsis width', () => {
expect(directive.width).toBe('100px');
});
test('should be enabled if abpEllipsisEnabled input is true', () => {
expect(directive.enabled).toBe(true);
});
test('should have given title', () => {
expect(directive.title).toBe('test title');
});
test('should have element innerText as title if not specified', () => {
spectator.setHostInput({ title: undefined });
expect(directive.title).toBe(el.innerText);
});
test('should add abp-ellipsis-inline class to element if width is given', () => {
expect(el).toHaveClass('abp-ellipsis-inline');
});
test('should add abp-ellipsis class to element if width is not given', () => {
spectator.setHostInput({ width: undefined });
expect(el).toHaveClass('abp-ellipsis');
});
});

39
npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts

@ -0,0 +1,39 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { ClickEventStopPropagationDirective } from '../directives/stop-propagation.directive';
describe('ClickEventStopPropagationDirective', () => {
let spectator: SpectatorDirective<ClickEventStopPropagationDirective>;
let directive: ClickEventStopPropagationDirective;
let link: HTMLAnchorElement;
let childClickEventFn = jest.fn(() => null);
let parentClickEventFn = jest.fn(() => null);
const createDirective = createDirectiveFactory({
directive: ClickEventStopPropagationDirective,
});
beforeEach(() => {
spectator = createDirective(
'<div (click)="parentClickEventFn()"><a (click.stop)="childClickEventFn()" >Link</a></div>',
{
hostProps: { parentClickEventFn, childClickEventFn },
},
);
directive = spectator.directive;
link = spectator.query('a');
childClickEventFn.mockClear();
parentClickEventFn.mockClear();
});
test('should be created', () => {
expect(directive).toBeTruthy();
});
test('should not call click event of parent when child element is clicked', done => {
spectator.setHostInput({ parentClickEventFn, childClickEventFn });
spectator.click('a');
spectator.detectChanges();
expect(childClickEventFn).toHaveBeenCalled();
expect(parentClickEventFn).not.toHaveBeenCalled();
done();
});
});

1
npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts

@ -44,6 +44,7 @@ export class ModalComponent implements OnDestroy {
} else {
this.renderer.removeClass(document.body, 'modal-open');
this.disappear.emit();
this.destroy$.next();
}
}

Loading…
Cancel
Save