Merge branch 'dev' into Align-Option-Class-Names

pull/1919/head
Yunus Emre Kalkan 5 years ago
commit b3d4d279d9

@ -93,7 +93,8 @@ An example mapping configuration is shown below:
````js
mappings: {
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/",
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/"
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/",
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/"
}
````

@ -92,6 +92,7 @@
"prettier --write",
"tslint --fix",
"git add"
]
],
"dist/*": []
}
}

@ -9,7 +9,7 @@ import {
OnChanges,
TemplateRef,
TrackByFunction,
ViewContainerRef
ViewContainerRef,
} from '@angular/core';
import compare from 'just-compare';
import clone from 'just-clone';
@ -25,7 +25,7 @@ class RecordView {
}
@Directive({
selector: '[abpFor]'
selector: '[abpFor]',
})
export class ForDirective implements OnChanges {
@Input('abpForOf')
@ -67,7 +67,7 @@ export class ForDirective implements OnChanges {
constructor(
private tempRef: TemplateRef<AbpForContext>,
private vcRef: ViewContainerRef,
private differs: IterableDiffers
private differs: IterableDiffers,
) {}
private iterateOverAppliedOperations(changes: IterableChanges<any>) {
@ -78,7 +78,7 @@ export class ForDirective implements OnChanges {
const view = this.vcRef.createEmbeddedView(
this.tempRef,
new AbpForContext(null, -1, -1, this.items),
currentIndex
currentIndex,
);
rw.push(new RecordView(record, view));
@ -155,7 +155,7 @@ export class ForDirective implements OnChanges {
const compareFn = this.compareFn;
if (typeof this.filterBy !== 'undefined') {
if (typeof this.filterBy !== 'undefined' && this.filterVal) {
items = items.filter(item => compareFn(item[this.filterBy], this.filterVal));
}

@ -3,7 +3,7 @@ import { Subject } from 'rxjs';
import snq from 'snq';
@Directive({
selector: '[abpVisibility]'
selector: '[abpVisibility]',
})
export class VisibilityDirective implements AfterViewInit {
@Input('abpVisibility')
@ -17,6 +17,10 @@ export class VisibilityDirective implements AfterViewInit {
constructor(@Optional() private elRef: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit() {
if (!this.focusedElement && this.elRef) {
this.focusedElement = this.elRef.nativeElement;
}
let observer: MutationObserver;
if (this.mutationObserverEnabled) {
observer = new MutationObserver(mutations => {
@ -25,7 +29,7 @@ export class VisibilityDirective implements AfterViewInit {
const htmlNodes = snq(
() => Array.from(mutation.target.childNodes).filter(node => node instanceof HTMLElement),
[]
[],
);
if (!htmlNodes.length) {
@ -40,13 +44,13 @@ export class VisibilityDirective implements AfterViewInit {
});
observer.observe(this.focusedElement, {
childList: true
childList: true,
});
} else {
setTimeout(() => {
const htmlNodes = snq(
() => Array.from(this.focusedElement.childNodes).filter(node => node instanceof HTMLElement),
[]
[],
);
if (!htmlNodes.length) this.removeFromDOM();

@ -35,7 +35,7 @@ export class SessionState {
}
@Action(SetTenant)
setTenantId({ patchState }: StateContext<Session.State>, { payload }: SetTenant) {
setTenant({ patchState }: StateContext<Session.State>, { payload }: SetTenant) {
patchState({
tenant: payload,
});

@ -109,7 +109,7 @@ export const CONFIG_STATE_DATA = {
},
} as Config.State;
describe('ConfigService', () => {
describe('ConfigState', () => {
let spectator: SpectatorService<ConfigService>;
let store: SpyObject<Store>;
let service: ConfigService;

@ -0,0 +1,191 @@
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest';
import { ForDirective } from '../directives/for.directive';
import { uuid } from '../utils';
describe('ForDirective', () => {
let spectator: SpectatorDirective<ForDirective>;
let directive: ForDirective;
const items = [0, 1, 2, 3, 4, 5];
const createDirective = createDirectiveFactory({
directive: ForDirective,
});
describe('basic', () => {
beforeEach(() => {
spectator = createDirective('<ul><li *abpFor="let item of items">{{ item }}</li></ul>', {
hostProps: { items },
});
directive = spectator.directive;
});
test('should be created', () => {
expect(directive).toBeTruthy();
});
test('should be iterated', () => {
const elements = spectator.queryAll('li');
expect(elements[3]).toHaveText('3');
expect(elements).toHaveLength(6);
});
test('should sync the DOM when change items', () => {
(spectator.hostComponent as any).items = [10, 11, 12];
spectator.detectChanges();
const elements = spectator.queryAll('li');
expect(elements[1]).toHaveText('11');
expect(elements).toHaveLength(3);
});
test('should sync the DOM when add an item', () => {
(spectator.hostComponent as any).items = [...items, 6];
spectator.detectChanges();
const elements = spectator.queryAll('li');
expect(elements[6]).toHaveText('6');
expect(elements).toHaveLength(7);
});
});
describe('trackBy', () => {
const trackByFn = (_, item) => item;
beforeEach(() => {
spectator = createDirective('<ul><li *abpFor="let item of items; trackBy: trackByFn">{{ item }}</li></ul>', {
hostProps: { items, trackByFn },
});
directive = spectator.directive;
});
test('should be setted the trackBy', () => {
expect(directive.trackBy).toEqual(trackByFn);
});
});
describe('with basic order', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of [3,6,2];
orderDir: 'ASC'">
{{ item }}
</li>
</ul>`,
);
directive = spectator.directive;
});
test('should order by asc', () => {
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['2', '3', '6']);
});
});
describe('with order', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of [{value: 3}, {value: 6}, {value: 2}];
orderBy: 'value';
orderDir: orderDir">
{{ item.value }}
</li>
</ul>`,
{
hostProps: { orderDir: 'ASC' },
},
);
directive = spectator.directive;
});
test('should order by asc', () => {
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['2', '3', '6']);
});
test('should order by desc', () => {
(spectator.hostComponent as any).orderDir = 'DESC';
spectator.detectChanges();
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['6', '3', '2']);
});
});
describe('with filter', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of [{value: 'test'}, {value: 'abp'}, {value: 'volo'}];
filterBy: 'value';
filterVal: filterVal">
{{ item.value }}
</li>
</ul>`,
{
hostProps: { filterVal: '' },
},
);
directive = spectator.directive;
});
test('should not filter when filterVal is empty,', () => {
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['test', 'abp', 'volo']);
});
test('should be filtered', () => {
(spectator.hostComponent as any).filterVal = 'volo';
spectator.detectChanges();
expect(spectator.query('li')).toHaveText('volo');
});
test('should not show an element when filter value not match to any text', () => {
(spectator.hostComponent as any).filterVal = 'volos';
spectator.detectChanges();
const elements = spectator.queryAll('li');
expect(elements).toHaveLength(0);
});
});
describe('with empty ref', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of items;
emptyRef: empty">
{{ item.value }}
</li>
<ng-template #empty>No records found</ng-template>
</ul>`,
{
hostProps: { items: [] },
},
);
directive = spectator.directive;
});
test('should display the empty ref', () => {
expect(spectator.query('ul')).toHaveText('No records found');
expect(spectator.queryAll('li')).toHaveLength(0);
});
test('should not display the empty ref', () => {
expect(spectator.query('ul')).toHaveText('No records found');
expect(spectator.queryAll('li')).toHaveLength(0);
(spectator.hostComponent as any).items = [0];
spectator.detectChanges();
expect(spectator.query('ul')).not.toHaveText('No records found');
expect(spectator.queryAll('li')).toHaveLength(1);
});
});
});

@ -0,0 +1,74 @@
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { Session } from '../models/session';
import { ProfileService } from '../services';
import { ProfileState } from '../states';
import { GetAppConfiguration } from '../actions/config.actions';
import { of } from 'rxjs';
import { Profile } from '../models/profile';
export class DummyClass {}
export const PROFILE_STATE_DATA = {
profile: { userName: 'admin', email: 'info@abp.io', name: 'Admin' },
} as Profile.State;
describe('ProfileState', () => {
let spectator: SpectatorService<DummyClass>;
let state: ProfileState;
let profileService: SpyObject<ProfileService>;
let patchedData;
const patchState = jest.fn(data => (patchedData = data));
const createService = createServiceFactory({
service: DummyClass,
mocks: [ProfileService],
});
beforeEach(() => {
spectator = createService();
profileService = spectator.get(ProfileService);
state = new ProfileState(profileService);
});
describe('#getProfile', () => {
it('should return the current language', () => {
expect(ProfileState.getProfile(PROFILE_STATE_DATA)).toEqual(PROFILE_STATE_DATA.profile);
});
});
describe('#GetProfile', () => {
it('should call the profile service get method and update the state', () => {
const mockData = { userName: 'test', email: 'test@abp.io' };
const spy = jest.spyOn(profileService, 'get');
spy.mockReturnValue(of(mockData as any));
state.profileGet({ patchState } as any).subscribe();
expect(patchedData).toEqual({ profile: mockData });
});
});
describe('#UpdateProfile', () => {
it('should call the profile service update method and update the state', () => {
const mockData = { userName: 'test2', email: 'test@abp.io' };
const spy = jest.spyOn(profileService, 'update');
spy.mockReturnValue(of(mockData as any));
state.profileUpdate({ patchState } as any, { payload: mockData as any }).subscribe();
expect(patchedData).toEqual({ profile: mockData });
});
});
describe('#ChangePassword', () => {
it('should call the profile service changePassword method', () => {
const mockData = { currentPassword: 'test123', newPassword: 'test123' };
const spy = jest.spyOn(profileService, 'changePassword');
spy.mockReturnValue(of(null));
state.changePassword(null, { payload: mockData }).subscribe();
expect(spy).toHaveBeenCalledWith(mockData, true);
});
});
});

@ -0,0 +1,70 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { Session } from '../models/session';
import { LocalizationService } from '../services';
import { SessionState } from '../states';
import { GetAppConfiguration } from '../actions/config.actions';
import { of } from 'rxjs';
export class DummyClass {}
export const SESSION_STATE_DATA = {
language: 'tr',
tenant: { id: 'd5692aef-2ac6-49cd-9f3e-394c0bd4f8b3', name: 'Test' },
} as Session.State;
describe('SessionState', () => {
let spectator: SpectatorService<DummyClass>;
let state: SessionState;
const createService = createServiceFactory({
service: DummyClass,
mocks: [LocalizationService],
});
beforeEach(() => {
spectator = createService();
state = new SessionState(spectator.get(LocalizationService));
});
describe('#getLanguage', () => {
it('should return the current language', () => {
expect(SessionState.getLanguage(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.language);
});
});
describe('#getTenant', () => {
it('should return the tenant object', () => {
expect(SessionState.getTenant(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.tenant);
});
});
describe('#SetLanguage', () => {
it('should set the language and dispatch the GetAppConfiguration action', () => {
let patchedData;
let dispatchedData;
const patchState = jest.fn(data => (patchedData = data));
const dispatch = jest.fn(action => {
dispatchedData = action;
return of({});
});
const spy = jest.spyOn(spectator.get(LocalizationService), 'registerLocale');
state.setLanguage({ patchState, dispatch } as any, { payload: 'en' }).subscribe();
expect(patchedData).toEqual({ language: 'en' });
expect(dispatchedData instanceof GetAppConfiguration).toBeTruthy();
expect(spy).toHaveBeenCalledWith('en');
});
});
describe('#setTenantId', () => {
it('should set the tenant', () => {
let patchedData;
const patchState = jest.fn(data => (patchedData = data));
const testTenant = { id: '54ae02ba-9289-4c1b-8521-0ea437756288', name: 'Test Tenant' };
state.setTenant({ patchState } as any, { payload: testTenant });
expect(patchedData).toEqual({ tenant: testTenant });
});
});
});

@ -0,0 +1,128 @@
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 mutation observer and without content', () => {
beforeEach(() => {
spectator = createDirective('<div [abpVisibility] [mutationObserverEnabled]="false"></div>');
directive = spectator.directive;
});
it('should be created', () => {
expect(directive).toBeTruthy();
});
it('should be removed', done => {
setTimeout(() => {
expect(spectator.query('div')).toBeFalsy();
done();
}, 0);
});
});
describe('without mutation observer and with content', () => {
beforeEach(() => {
spectator = createDirective(
'<div [abpVisibility] [mutationObserverEnabled]="false"><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" [mutationObserverEnabled]="false"></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" [mutationObserverEnabled]="false"></div><div #container></div>',
);
directive = spectator.directive;
});
it('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;
});
it('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;
});
it('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,6 +1,6 @@
import { ABP } from '@abp/ng.core';
import { ConfirmationService, Toaster } from '@abp/ng.theme.shared';
import { Component, TemplateRef, ViewChild } from '@angular/core';
import { Component, TemplateRef, ViewChild, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
@ -13,7 +13,7 @@ import { IdentityState } from '../../states/identity.state';
selector: 'abp-roles',
templateUrl: './roles.component.html',
})
export class RolesComponent {
export class RolesComponent implements OnInit {
@Select(IdentityState.getRoles)
data$: Observable<Identity.RoleItem[]>;
@ -45,6 +45,10 @@ export class RolesComponent {
constructor(private confirmationService: ConfirmationService, private fb: FormBuilder, private store: Store) {}
ngOnInit() {
this.get();
}
onSearch(value) {
this.pageQuery.filter = value;
this.get();

@ -112,7 +112,11 @@
</ng-template>
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()">
<ng-template #loaderRef
><div class="text-center"><i class="fa fa-pulse fa-spinner"></i></div
></ng-template>
<form *ngIf="form; else loaderRef" [formGroup]="form" (ngSubmit)="save()">
<ngb-tabset>
<ngb-tab [title]="'AbpIdentity::UserInformations' | abpLocalization">
<ng-template ngbTabContent>
@ -210,7 +214,7 @@
<button type="button" class="btn btn-secondary" #abpClose>
{{ 'AbpIdentity::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" (click)="save()" [disabled]="form.invalid">{{
<abp-button iconClass="fa fa-check" (click)="save()" [disabled]="form?.invalid">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button>
</ng-template>

@ -1,6 +1,6 @@
import { ABP } from '@abp/ng.core';
import { ConfirmationService, Toaster } from '@abp/ng.theme.shared';
import { Component, TemplateRef, TrackByFunction, ViewChild } from '@angular/core';
import { Component, TemplateRef, TrackByFunction, ViewChild, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
@ -13,6 +13,7 @@ import {
GetUserRoles,
GetUsers,
UpdateUser,
GetRoles,
} from '../../actions/identity.actions';
import { Identity } from '../../models/identity';
import { IdentityState } from '../../states/identity.state';
@ -20,7 +21,7 @@ import { IdentityState } from '../../states/identity.state';
selector: 'abp-users',
templateUrl: './users.component.html',
})
export class UsersComponent {
export class UsersComponent implements OnInit {
@Select(IdentityState.getUsers)
data$: Observable<Identity.UserItem[]>;
@ -62,32 +63,39 @@ export class UsersComponent {
constructor(private confirmationService: ConfirmationService, private fb: FormBuilder, private store: Store) {}
ngOnInit() {
this.get();
}
onSearch(value) {
this.pageQuery.filter = value;
this.get();
}
buildForm() {
this.roles = this.store.selectSnapshot(IdentityState.getRoles);
this.form = this.fb.group({
userName: [this.selected.userName || '', [Validators.required, Validators.maxLength(256)]],
email: [this.selected.email || '', [Validators.required, Validators.email, Validators.maxLength(256)]],
name: [this.selected.name || '', [Validators.maxLength(64)]],
surname: [this.selected.surname || '', [Validators.maxLength(64)]],
phoneNumber: [this.selected.phoneNumber || '', [Validators.maxLength(16)]],
lockoutEnabled: [this.selected.twoFactorEnabled || (this.selected.id ? false : true)],
twoFactorEnabled: [this.selected.twoFactorEnabled || (this.selected.id ? false : true)],
roleNames: this.fb.array(
this.roles.map(role =>
this.fb.group({
[role.name]: [!!snq(() => this.selectedUserRoles.find(userRole => userRole.id === role.id))],
}),
this.store.dispatch(new GetRoles()).subscribe(() => {
this.roles = this.store.selectSnapshot(IdentityState.getRoles);
this.form = this.fb.group({
userName: [this.selected.userName || '', [Validators.required, Validators.maxLength(256)]],
email: [this.selected.email || '', [Validators.required, Validators.email, Validators.maxLength(256)]],
name: [this.selected.name || '', [Validators.maxLength(64)]],
surname: [this.selected.surname || '', [Validators.maxLength(64)]],
phoneNumber: [this.selected.phoneNumber || '', [Validators.maxLength(16)]],
lockoutEnabled: [this.selected.twoFactorEnabled || (this.selected.id ? false : true)],
twoFactorEnabled: [this.selected.twoFactorEnabled || (this.selected.id ? false : true)],
roleNames: this.fb.array(
this.roles.map(role =>
this.fb.group({
[role.name]: [!!snq(() => this.selectedUserRoles.find(userRole => userRole.id === role.id))],
}),
),
),
),
});
if (!this.selected.userName) {
this.form.addControl('password', new FormControl('', [Validators.required, Validators.maxLength(32)]));
}
});
if (!this.selected.userName) {
this.form.addControl('password', new FormControl('', [Validators.required, Validators.maxLength(32)]));
}
}
openModal() {

@ -1,10 +1,8 @@
import { AuthGuard, DynamicLayoutComponent, PermissionGuard } from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RouterModule, Routes } from '@angular/router';
import { RolesComponent } from './components/roles/roles.component';
import { RoleResolver } from './resolvers/roles.resolver';
import { DynamicLayoutComponent, AuthGuard, PermissionGuard } from '@abp/ng.core';
import { UsersComponent } from './components/users/users.component';
import { UserResolver } from './resolvers/users.resolver';
const routes: Routes = [
{ path: '', redirectTo: 'roles', pathMatch: 'full' },
@ -16,14 +14,12 @@ const routes: Routes = [
{
path: 'roles',
component: RolesComponent,
resolve: [RoleResolver],
data: { requiredPolicy: 'AbpIdentity.Roles' },
},
{
path: 'users',
component: UsersComponent,
data: { requiredPolicy: 'AbpIdentity.Users' },
resolve: [RoleResolver, UserResolver],
},
],
},
@ -32,6 +28,5 @@ const routes: Routes = [
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [RoleResolver, UserResolver],
})
export class IdentityRoutingModule {}

@ -1,16 +0,0 @@
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Store } from '@ngxs/store';
import { GetRoles } from '../actions/identity.actions';
import { Identity } from '../models/identity';
import { IdentityState } from '../states/identity.state';
@Injectable()
export class RoleResolver implements Resolve<Identity.State> {
constructor(private store: Store) {}
resolve() {
const roles = this.store.selectSnapshot(IdentityState.getRoles);
return roles && roles.length ? null : this.store.dispatch(new GetRoles());
}
}

@ -1,16 +0,0 @@
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Store } from '@ngxs/store';
import { GetUsers } from '../actions/identity.actions';
import { Identity } from '../models/identity';
import { IdentityState } from '../states/identity.state';
@Injectable()
export class UserResolver implements Resolve<Identity.State> {
constructor(private store: Store) {}
resolve() {
const users = this.store.selectSnapshot(IdentityState.getUsers);
return users && users.length ? null : this.store.dispatch(new GetUsers());
}
}

@ -23,22 +23,22 @@ import { IdentityService } from '../services/identity.service';
export class IdentityState {
@Selector()
static getRoles({ roles }: Identity.State): Identity.RoleItem[] {
return roles.items;
return roles.items || [];
}
@Selector()
static getRolesTotalCount({ roles }: Identity.State): number {
return roles.totalCount;
return roles.totalCount || 0;
}
@Selector()
static getUsers({ users }: Identity.State): Identity.UserItem[] {
return users.items;
return users.items || [];
}
@Selector()
static getUsersTotalCount({ users }: Identity.State): number {
return users.totalCount;
return users.totalCount || 0;
}
constructor(private identityService: IdentityService) {}

@ -7,6 +7,5 @@ export * from './lib/actions/identity.actions';
export * from './lib/components/roles/roles.component';
export * from './lib/constants/routes';
export * from './lib/models/identity';
export * from './lib/resolvers/roles.resolver';
export * from './lib/services/identity.service';
export * from './lib/states/identity.state';

@ -1,15 +1,15 @@
import { ABP } from '@abp/ng.core';
import { ConfirmationService, Toaster } from '@abp/ng.theme.shared';
import { Component, TemplateRef, ViewChild } from '@angular/core';
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
import { debounceTime, finalize, pluck, switchMap, take } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { finalize, pluck, switchMap, take } from 'rxjs/operators';
import {
CreateTenant,
DeleteTenant,
GetTenants,
GetTenantById,
GetTenants,
UpdateTenant,
} from '../../actions/tenant-management.actions';
import { TenantManagementService } from '../../services/tenant-management.service';
@ -25,7 +25,7 @@ interface SelectedModalContent {
selector: 'abp-tenants',
templateUrl: './tenants.component.html',
})
export class TenantsComponent {
export class TenantsComponent implements OnInit {
@Select(TenantManagementState.get)
data$: Observable<ABP.BasicItem[]>;
@ -81,6 +81,10 @@ export class TenantsComponent {
private store: Store,
) {}
ngOnInit() {
this.get();
}
onSearch(value) {
this.pageQuery.filter = value;
this.get();

@ -1,16 +0,0 @@
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Store } from '@ngxs/store';
import { GetTenants } from '../actions/tenant-management.actions';
import { TenantManagement } from '../models/tenant-management';
import { TenantManagementState } from '../states/tenant-management.state';
@Injectable()
export class TenantsResolver implements Resolve<TenantManagement.State> {
constructor(private store: Store) {}
resolve() {
const data = this.store.selectSnapshot(TenantManagementState.get);
return data && data.length ? null : this.store.dispatch(new GetTenants());
}
}

@ -1,7 +1,6 @@
import { AuthGuard, DynamicLayoutComponent, PermissionGuard } from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TenantsResolver } from './resolvers/tenants.resolver';
import { TenantsComponent } from './components/tenants/tenants.component';
const routes: Routes = [
@ -11,13 +10,12 @@ const routes: Routes = [
component: DynamicLayoutComponent,
canActivate: [AuthGuard, PermissionGuard],
data: { requiredPolicy: 'AbpTenantManagement.Tenants' },
children: [{ path: '', component: TenantsComponent, resolve: [TenantsResolver] }],
children: [{ path: '', component: TenantsComponent }],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [TenantsResolver],
})
export class TenantManagementRoutingModule {}

@ -3,6 +3,5 @@ export * from './lib/actions';
export * from './lib/components';
export * from './lib/constants';
export * from './lib/models';
export * from './lib/resolvers';
export * from './lib/services';
export * from './lib/states';

@ -1,14 +1,24 @@
import { Component, Input } from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewChild, ElementRef, Renderer2, OnInit } from '@angular/core';
import { ABP } from '@abp/ng.core';
@Component({
selector: 'abp-button',
// tslint:disable-next-line: component-max-inline-declarations
template: `
<button [attr.type]="type" [ngClass]="buttonClass" [disabled]="loading || disabled">
<button
#button
[attr.type]="type"
[ngClass]="buttonClass"
[disabled]="loading || disabled"
(click)="click.emit($event)"
(focus)="focus.emit($event)"
(blur)="blur.emit($event)"
>
<i [ngClass]="icon" class="mr-1"></i><ng-content></ng-content>
</button>
`,
})
export class ButtonComponent {
export class ButtonComponent implements OnInit {
@Input()
buttonClass = 'btn btn-primary';
@ -24,6 +34,21 @@ export class ButtonComponent {
@Input()
disabled = false;
@Input()
attributes: ABP.Dictionary<string>;
// tslint:disable-next-line: no-output-native
@Output() readonly click = new EventEmitter<MouseEvent>();
// tslint:disable-next-line: no-output-native
@Output() readonly focus = new EventEmitter<FocusEvent>();
// tslint:disable-next-line: no-output-native
@Output() readonly blur = new EventEmitter<FocusEvent>();
@ViewChild('button', { static: true })
buttonRef: ElementRef<HTMLButtonElement>;
/**
* @deprecated Use buttonType instead. To be deleted in v1
*/
@ -32,4 +57,14 @@ export class ButtonComponent {
get icon(): string {
return `${this.loading ? 'fa fa-pulse fa-spinner' : this.iconClass || 'd-none'}`;
}
constructor(private renderer: Renderer2) {}
ngOnInit() {
if (this.attributes) {
Object.keys(this.attributes).forEach(key => {
this.renderer.setAttribute(this.buttonRef.nativeElement, key, this.attributes[key]);
});
}
}
}

@ -2,46 +2,55 @@ import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { ButtonComponent } from '../components';
describe('ButtonComponent', () => {
let host: SpectatorHost<ButtonComponent>;
let spectator: SpectatorHost<ButtonComponent>;
const createHost = createHostFactory(ButtonComponent);
beforeEach(() => (host = createHost('<abp-button iconClass="fa fa-check">Button</abp-button>')));
beforeEach(
() =>
(spectator = createHost('<abp-button iconClass="fa fa-check" [attributes]="attributes">Button</abp-button>', {
hostProps: { attributes: { autofocus: '', name: 'abp-button' } },
})),
);
it('should display the button', () => {
expect(host.query('button')).toBeTruthy();
expect(spectator.query('button')).toBeTruthy();
});
it('should equal the default classes to btn btn-primary', () => {
expect(host.query('button')).toHaveClass('btn btn-primary');
expect(spectator.query('button')).toHaveClass('btn btn-primary');
});
it('should equal the default type to button', () => {
expect(host.query('button')).toHaveAttribute('type', 'button');
expect(spectator.query('button')).toHaveAttribute('type', 'button');
});
it('should enabled', () => {
expect(host.query('[disabled]')).toBeFalsy();
expect(spectator.query('[disabled]')).toBeFalsy();
});
it('should have the text content', () => {
expect(host.query('button')).toHaveText('Button');
expect(spectator.query('button')).toHaveText('Button');
});
it('should display the icon', () => {
expect(host.query('i.d-none')).toBeFalsy();
expect(host.query('i')).toHaveClass('fa');
expect(spectator.query('i.d-none')).toBeFalsy();
expect(spectator.query('i')).toHaveClass('fa');
});
it('should display the spinner icon', () => {
host.component.loading = true;
host.detectComponentChanges();
expect(host.query('i')).toHaveClass('fa-spinner');
spectator.component.loading = true;
spectator.detectComponentChanges();
expect(spectator.query('i')).toHaveClass('fa-spinner');
});
it('should disabled when the loading input is true', () => {
host.component.loading = true;
host.detectComponentChanges();
expect(host.query('[disabled]')).toBeDefined();
spectator.component.loading = true;
spectator.detectComponentChanges();
expect(spectator.query('[disabled]')).toBeTruthy();
});
it('should disabled when the loading input is true', () => {
expect(spectator.query('[autofocus][name="abp-button"]')).toBeTruthy();
});
});

@ -37,7 +37,7 @@ import fse from 'fs-extra';
});
await execa('git', ['add', '../dist/*', '../package.json'], { stdout: 'inherit' });
await execa('git', ['commit', '-m', 'Build ng packages'], { stdout: 'inherit' });
await execa('git', ['commit', '--no-verify', '-m', 'Build ng packages'], { stdout: 'inherit' });
process.exit(0);
})();

@ -18,17 +18,14 @@
"@abp/ng.setting-management": "^0.9.1",
"@abp/ng.tenant-management": "^0.9.1",
"@abp/ng.theme.basic": "^0.9.1",
"@angular/animations": "~8.2.10",
"@angular/common": "~8.2.10",
"@angular/compiler": "~8.2.10",
"@angular/core": "~8.2.10",
"@angular/forms": "~8.2.10",
"@angular/platform-browser": "~8.2.10",
"@angular/platform-browser-dynamic": "~8.2.10",
"@angular/router": "~8.2.10",
"@angularclass/hmr": "^2.1.3",
"@ngxs/devtools-plugin": "^3.5.0",
"@ngxs/hmr-plugin": "^3.5.0",
"@angular/animations": "~8.2.11",
"@angular/common": "~8.2.11",
"@angular/compiler": "~8.2.11",
"@angular/core": "~8.2.11",
"@angular/forms": "~8.2.11",
"@angular/platform-browser": "~8.2.11",
"@angular/platform-browser-dynamic": "~8.2.11",
"@angular/router": "~8.2.11",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
@ -36,19 +33,22 @@
"devDependencies": {
"@angular-devkit/build-angular": "~0.803.9",
"@angular/cli": "~8.3.9",
"@angular/compiler-cli": "~8.2.10",
"@angular/language-service": "~8.2.10",
"@angular/compiler-cli": "~8.2.11",
"@angular/language-service": "~8.2.11",
"@angularclass/hmr": "^2.1.3",
"@ngxs/hmr-plugin": "^3.5.0",
"@ngxs/logger-plugin": "^3.5.1",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"karma-jasmine": "~2.0.1",
"karma": "~4.1.0",
"ngxs-schematic": "^1.1.9",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",

@ -3,7 +3,7 @@ import { LAYOUTS } from '@abp/ng.theme.basic';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
import { NgxsModule } from '@ngxs/store';
import { OAuthModule } from 'angular-oauth2-oidc';
import { environment } from '../environments/environment';
@ -16,16 +16,18 @@ import { IdentityConfigModule } from '@abp/ng.identity.config';
import { TenantManagementConfigModule } from '@abp/ng.tenant-management.config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management.config';
const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })];
@NgModule({
declarations: [AppComponent],
imports: [
ThemeSharedModule.forRoot(),
CoreModule.forRoot({
environment,
requirements: {
layouts: LAYOUTS,
},
}),
ThemeSharedModule.forRoot(),
OAuthModule.forRoot(),
NgxsModule.forRoot([]),
AccountConfigModule.forRoot({ redirectUrl: '/' }),
@ -37,7 +39,7 @@ import { SettingManagementConfigModule } from '@abp/ng.setting-management.config
AppRoutingModule,
SharedModule,
NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production }),
...(environment.production ? [] : LOGGERS),
],
bootstrap: [AppComponent],
})

Loading…
Cancel
Save