another intermediate commit

This commit is contained in:
lotte
2018-09-06 11:55:13 +02:00
parent 75a3985474
commit 14f5c97ecc
44 changed files with 1004 additions and 1994 deletions

13
angular.json Normal file
View File

@@ -0,0 +1,13 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"defaultCollection": "@ngrx/schematics"
},
"projects": {
"core": {
"root": "",
"projectType": "application"
}
}
}

View File

@@ -41,14 +41,14 @@
"server:watch": "nodemon dist/server.js", "server:watch": "nodemon dist/server.js",
"server:watch:debug": "nodemon --debug dist/server.js", "server:watch:debug": "nodemon --debug dist/server.js",
"webpack:watch": "webpack -w --mode development", "webpack:watch": "webpack -w --mode development",
"watch": "yarn run build && npm-run-all -p webpack:watch server:watch", "watch": "yarn run build && npm-run-all -p webpack:watch server:watch --mode development",
"watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug", "watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug --mode development",
"predebug": "yarn run build", "predebug": "yarn run build",
"predebug:server": "yarn run build", "predebug:server": "yarn run build",
"debug": "node --debug-brk dist/server.js", "debug": "node --debug-brk dist/server.js",
"debug:server": "node-nightly --inspect --debug-brk dist/server.js", "debug:server": "node-nightly --inspect --debug-brk dist/server.js",
"debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js", "debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --mode development",
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server -p", "debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server --mode production",
"ci": "yarn run lint && yarn run build:aot && yarn run test:headless && npm-run-all -p -r server e2e", "ci": "yarn run lint && yarn run build:aot && yarn run test:headless && npm-run-all -p -r server e2e",
"protractor": "node node_modules/protractor/bin/protractor", "protractor": "node node_modules/protractor/bin/protractor",
"pree2e": "yarn run webdriver:update", "pree2e": "yarn run webdriver:update",
@@ -64,6 +64,7 @@
}, },
"dependencies": { "dependencies": {
"@angular/animations": "^6.1.4", "@angular/animations": "^6.1.4",
"@angular/cli": "^6.1.5",
"@angular/common": "^6.1.4", "@angular/common": "^6.1.4",
"@angular/core": "^6.1.4", "@angular/core": "^6.1.4",
"@angular/forms": "^6.1.4", "@angular/forms": "^6.1.4",
@@ -105,6 +106,7 @@
"methods": "1.1.2", "methods": "1.1.2",
"moment": "^2.22.1", "moment": "^2.22.1",
"morgan": "1.9.0", "morgan": "1.9.0",
"ng-mocks": "^6.2.1",
"ng2-file-upload": "1.2.1", "ng2-file-upload": "1.2.1",
"ng2-nouislider": "^1.7.11", "ng2-nouislider": "^1.7.11",
"ngx-bootstrap": "^3.0.1", "ngx-bootstrap": "^3.0.1",
@@ -114,7 +116,7 @@
"nouislider": "^11.0.0", "nouislider": "^11.0.0",
"pem": "1.12.3", "pem": "1.12.3",
"reflect-metadata": "0.1.12", "reflect-metadata": "0.1.12",
"rxjs": "^6.2.2", "rxjs": "6.2.2",
"sortablejs": "1.7.0", "sortablejs": "1.7.0",
"text-mask-core": "5.0.1", "text-mask-core": "5.0.1",
"ts-md5": "^1.2.4", "ts-md5": "^1.2.4",
@@ -126,8 +128,11 @@
"devDependencies": { "devDependencies": {
"@angular/compiler": "^6.1.4", "@angular/compiler": "^6.1.4",
"@angular/compiler-cli": "^6.1.4", "@angular/compiler-cli": "^6.1.4",
"@ngrx/entity": "^6.1.0",
"@ngrx/schematics": "^6.1.0",
"@ngrx/store-devtools": "^6.1.0", "@ngrx/store-devtools": "^6.1.0",
"@ngtools/webpack": "^6.1.5", "@ngtools/webpack": "^6.1.5",
"@schematics/angular": "^0.7.5",
"@types/acorn": "^4.0.3", "@types/acorn": "^4.0.3",
"@types/cookie-parser": "1.4.1", "@types/cookie-parser": "1.4.1",
"@types/deep-freeze": "0.1.1", "@types/deep-freeze": "0.1.1",
@@ -160,14 +165,14 @@
"imports-loader": "0.8.0", "imports-loader": "0.8.0",
"istanbul-instrumenter-loader": "3.0.1", "istanbul-instrumenter-loader": "3.0.1",
"jasmine-core": "^3.2.1", "jasmine-core": "^3.2.1",
"jasmine-marbles": "0.3.0", "jasmine-marbles": "0.3.1",
"jasmine-spec-reporter": "4.2.1", "jasmine-spec-reporter": "4.2.1",
"karma": "2.0.0", "karma": "3.0.0",
"karma-chrome-launcher": "2.2.0", "karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1", "karma-cli": "1.0.1",
"karma-coverage": "1.1.1", "karma-coverage": "1.1.2",
"karma-istanbul-preprocessor": "0.0.2", "karma-istanbul-preprocessor": "0.0.2",
"karma-jasmine": "1.1.1", "karma-jasmine": "1.1.2",
"karma-mocha-reporter": "2.2.5", "karma-mocha-reporter": "2.2.5",
"karma-phantomjs-launcher": "1.0.4", "karma-phantomjs-launcher": "1.0.4",
"karma-remap-coverage": "^0.1.5", "karma-remap-coverage": "^0.1.5",
@@ -178,12 +183,12 @@
"ngrx-store-freeze": "^0.2.4", "ngrx-store-freeze": "^0.2.4",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
"nodemon": "^1.15.0", "nodemon": "^1.15.0",
"npm-run-all": "4.1.2", "npm-run-all": "4.1.3",
"postcss": "^6.0.18", "postcss": "^7.0.2",
"postcss-apply": "0.8.0", "postcss-apply": "0.11.0",
"postcss-cli": "^5.0.0", "postcss-cli": "^6.0.0",
"postcss-cssnext": "3.1.0", "postcss-cssnext": "3.1.0",
"postcss-loader": "^2.1.0", "postcss-loader": "^3.0.0",
"postcss-responsive-type": "1.0.0", "postcss-responsive-type": "1.0.0",
"postcss-smart-import": "0.7.6", "postcss-smart-import": "0.7.6",
"protractor": "^5.3.0", "protractor": "^5.3.0",
@@ -196,7 +201,7 @@
"rollup-plugin-node-globals": "1.2.1", "rollup-plugin-node-globals": "1.2.1",
"rollup-plugin-node-resolve": "^3.0.3", "rollup-plugin-node-resolve": "^3.0.3",
"rollup-plugin-terser": "^2.0.2", "rollup-plugin-terser": "^2.0.2",
"sass-loader": "6.0.6", "sass-loader": "7.1.0",
"script-ext-html-webpack-plugin": "2.0.1", "script-ext-html-webpack-plugin": "2.0.1",
"source-map": "0.7.3", "source-map": "0.7.3",
"source-map-loader": "0.2.4", "source-map-loader": "0.2.4",

View File

@@ -4,7 +4,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import 'rxjs/add/observable/of';
import { LoginPageComponent } from './login-page.component'; import { LoginPageComponent } from './login-page.component';

View File

@@ -1,4 +1,3 @@
import 'rxjs/add/observable/of';
import { PaginatedSearchOptions } from './paginated-search-options.model'; import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SearchOptions } from './search-options.model'; import { SearchOptions } from './search-options.model';
import { SearchFilter } from './search-filter.model'; import { SearchFilter } from './search-filter.model';

View File

@@ -3,7 +3,6 @@ import { SearchSidebarService } from './search-sidebar.service';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { async, TestBed } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import 'rxjs/add/observable/of';
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions'; import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowService } from '../../shared/host-window.service';
@@ -13,7 +12,7 @@ describe('SearchSidebarService', () => {
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
dispatch: {}, dispatch: {},
/* tslint:enable:no-empty */ /* tslint:enable:no-empty */
select: observableOf(true) pipe: observableOf(true)
}); });
const windowService = jasmine.createSpyObj('hostWindowService', const windowService = jasmine.createSpyObj('hostWindowService',
{ {

View File

@@ -4,7 +4,6 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Store, StoreModule } from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import { REQUEST } from '@nguniversal/express-engine/tokens'; import { REQUEST } from '@nguniversal/express-engine/tokens';
import 'rxjs/add/observable/of';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { authReducer, AuthState } from './auth.reducer'; import { authReducer, AuthState } from './auth.reducer';
@@ -27,7 +26,7 @@ describe('AuthService test', () => {
const mockStore: Store<AuthState> = jasmine.createSpyObj('store', { const mockStore: Store<AuthState> = jasmine.createSpyObj('store', {
dispatch: {}, dispatch: {},
select: observableOf(true) pipe: observableOf(true)
}); });
let authService: AuthService; let authService: AuthService;
const authRequest = new AuthRequestServiceStub(); const authRequest = new AuthRequestServiceStub();

View File

@@ -7,6 +7,7 @@ import { CoreState } from '../core.reducers';
import { ResourceType } from '../shared/resource-type'; import { ResourceType } from '../shared/resource-type';
import { NormalizedItem } from './models/normalized-item.model'; import { NormalizedItem } from './models/normalized-item.model';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import * as ngrx from '@ngrx/store';
describe('ObjectCacheService', () => { describe('ObjectCacheService', () => {
let service: ObjectCacheService; let service: ObjectCacheService;
@@ -52,7 +53,11 @@ describe('ObjectCacheService', () => {
describe('getBySelfLink', () => { describe('getBySelfLink', () => {
it('should return an observable of the cached object with the specified self link and type', () => { it('should return an observable of the cached object with the specified self link and type', () => {
spyOn(store, 'select').and.returnValue(observableOf(cacheEntry)); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(cacheEntry);
};
});
// due to the implementation of spyOn above, this subscribe will be synchronous // due to the implementation of spyOn above, this subscribe will be synchronous
service.getBySelfLink(selfLink).pipe(first()).subscribe((o) => { service.getBySelfLink(selfLink).pipe(first()).subscribe((o) => {
@@ -64,7 +69,11 @@ describe('ObjectCacheService', () => {
}); });
it('should not return a cached object that has exceeded its time to live', () => { it('should not return a cached object that has exceeded its time to live', () => {
spyOn(store, 'select').and.returnValue(observableOf(invalidCacheEntry)); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(invalidCacheEntry);
};
});
let getObsHasFired = false; let getObsHasFired = false;
const subscription = service.getBySelfLink(selfLink).subscribe((o) => getObsHasFired = true); const subscription = service.getBySelfLink(selfLink).subscribe((o) => getObsHasFired = true);
@@ -88,19 +97,31 @@ describe('ObjectCacheService', () => {
describe('has', () => { describe('has', () => {
it('should return true if the object with the supplied self link is cached and still valid', () => { it('should return true if the object with the supplied self link is cached and still valid', () => {
spyOn(store, 'select').and.returnValue(observableOf(cacheEntry)); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(cacheEntry);
};
});
expect(service.hasBySelfLink(selfLink)).toBe(true); expect(service.hasBySelfLink(selfLink)).toBe(true);
}); });
it("should return false if the object with the supplied self link isn't cached", () => { it("should return false if the object with the supplied self link isn't cached", () => {
spyOn(store, 'select').and.returnValue(observableOf(undefined)); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(undefined);
};
});
expect(service.hasBySelfLink(selfLink)).toBe(false); expect(service.hasBySelfLink(selfLink)).toBe(false);
}); });
it('should return false if the object with the supplied self link is cached but has exceeded its time to live', () => { it('should return false if the object with the supplied self link is cached but has exceeded its time to live', () => {
spyOn(store, 'select').and.returnValue(observableOf(invalidCacheEntry)); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(invalidCacheEntry);
};
});
expect(service.hasBySelfLink(selfLink)).toBe(false); expect(service.hasBySelfLink(selfLink)).toBe(false);
}); });

View File

@@ -6,6 +6,8 @@ import { CoreState } from '../core.reducers';
import { RestResponse } from './response-cache.models'; import { RestResponse } from './response-cache.models';
import { ResponseCacheEntry } from './response-cache.reducer'; import { ResponseCacheEntry } from './response-cache.reducer';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import * as ngrx from '@ngrx/store'
import { cold } from 'jasmine-marbles';
describe('ResponseCacheService', () => { describe('ResponseCacheService', () => {
let service: ResponseCacheService; let service: ResponseCacheService;
@@ -41,10 +43,11 @@ describe('ResponseCacheService', () => {
describe('get', () => { describe('get', () => {
it('should return an observable of the cached request with the specified key', () => { it('should return an observable of the cached request with the specified key', () => {
spyOn(store, 'select').and.callFake((...args: any[]) => { spyOnProperty(ngrx, 'select').and.callFake(() => {
return observableOf(validCacheEntry(keys[1])); return () => {
return () => observableOf(validCacheEntry(keys[1]));
};
}); });
let testObj: ResponseCacheEntry; let testObj: ResponseCacheEntry;
service.get(keys[1]).pipe(first()).subscribe((entry) => { service.get(keys[1]).pipe(first()).subscribe((entry) => {
testObj = entry; testObj = entry;
@@ -53,8 +56,10 @@ describe('ResponseCacheService', () => {
}); });
it('should not return a cached request that has exceeded its time to live', () => { it('should not return a cached request that has exceeded its time to live', () => {
spyOn(store, 'select').and.callFake((...args: any[]) => { spyOnProperty(ngrx, 'select').and.callFake(() => {
return observableOf(invalidCacheEntry(keys[1])); return () => {
return () => observableOf(invalidCacheEntry(keys[1]));
};
}); });
let getObsHasFired = false; let getObsHasFired = false;
@@ -66,17 +71,29 @@ describe('ResponseCacheService', () => {
describe('has', () => { describe('has', () => {
it('should return true if the request with the supplied key is cached and still valid', () => { it('should return true if the request with the supplied key is cached and still valid', () => {
spyOn(store, 'select').and.returnValue(observableOf(validCacheEntry(keys[1]))); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(validCacheEntry(keys[1]));
};
});
expect(service.has(keys[1])).toBe(true); expect(service.has(keys[1])).toBe(true);
}); });
it('should return false if the request with the supplied key isn\'t cached', () => { it('should return false if the request with the supplied key isn\'t cached', () => {
spyOn(store, 'select').and.returnValue(observableOf(undefined)); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(undefined);
};
});
expect(service.has(keys[1])).toBe(false); expect(service.has(keys[1])).toBe(false);
}); });
it('should return false if the request with the supplied key is cached but has exceeded its time to live', () => { it('should return false if the request with the supplied key is cached but has exceeded its time to live', () => {
spyOn(store, 'select').and.returnValue(observableOf(invalidCacheEntry(keys[1]))); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(invalidCacheEntry(keys[1]));
};
});
expect(service.has(keys[1])).toBe(false); expect(service.has(keys[1])).toBe(false);
}); });
}); });

View File

@@ -56,11 +56,11 @@ describe('ConfigService', () => {
} }
beforeEach(() => { beforeEach(() => {
scheduler = getTestScheduler();
responseCache = initMockResponseCacheService(true); responseCache = initMockResponseCacheService(true);
requestService = getMockRequestService(); requestService = getMockRequestService();
service = initTestService();
scheduler = getTestScheduler();
halService = new HALEndpointServiceStub(configEndpoint); halService = new HALEndpointServiceStub(configEndpoint);
service = initTestService();
}); });
describe('getConfigByHref', () => { describe('getConfigByHref', () => {

View File

@@ -1,4 +1,4 @@
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; import { Observable, of as observableOf, throwError as observableThrowError, merge as observableMerge } from 'rxjs';
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
@@ -18,18 +18,17 @@ export abstract class ConfigService {
protected abstract halService: HALEndpointService; protected abstract halService: HALEndpointService;
protected getConfig(request: RestRequest): Observable<ConfigData> { protected getConfig(request: RestRequest): Observable<ConfigData> {
return this.responseCache.get(request.href).pipe( const responses = this.responseCache.get(request.href).pipe(map((entry: ResponseCacheEntry) => entry.response));
map((entry: ResponseCacheEntry) => entry.response), const errorResponses = responses.pipe(
mergeMap((response) => { filter((response) => !response.isSuccessful),
if (response.isSuccessful && isNotEmpty(response) && isNotEmpty((response as ConfigSuccessResponse).configDefinition)) { mergeMap(() => observableThrowError(new Error(`Couldn't retrieve the config`)))
const configResponse = response as ConfigSuccessResponse;
return observableOf(new ConfigData(configResponse.pageInfo, configResponse.configDefinition));
} else if (!response.isSuccessful) {
return observableThrowError(new Error(`Couldn't retrieve the config`));
}
}),
distinctUntilChanged()
); );
const successResponses = responses.pipe(
filter((response) => response.isSuccessful && isNotEmpty(response) && isNotEmpty((response as ConfigSuccessResponse).configDefinition)),
map((response: ConfigSuccessResponse) => new ConfigData(response.pageInfo, response.configDefinition))
);
return observableMerge(errorResponses, successResponses);
} }
protected getConfigByNameHref(endpoint, resourceName): string { protected getConfigByNameHref(endpoint, resourceName): string {
@@ -73,7 +72,7 @@ export abstract class ConfigService {
map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)), map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)),
tap((request: RestRequest) => this.requestService.configure(request)), tap((request: RestRequest) => this.requestService.configure(request)),
mergeMap((request: RestRequest) => this.getConfig(request)), mergeMap((request: RestRequest) => this.getConfig(request)),
distinctUntilChanged(),); distinctUntilChanged());
} }
public getConfigByHref(href: string): Observable<ConfigData> { public getConfigByHref(href: string): Observable<ConfigData> {
@@ -91,10 +90,11 @@ export abstract class ConfigService {
map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)), map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)),
tap((request: RestRequest) => this.requestService.configure(request)), tap((request: RestRequest) => this.requestService.configure(request)),
mergeMap((request: RestRequest) => this.getConfig(request)), mergeMap((request: RestRequest) => this.getConfig(request)),
distinctUntilChanged(),); distinctUntilChanged());
} }
public getConfigBySearch(options: FindAllOptions = {}): Observable<ConfigData> { public getConfigBySearch(options: FindAllOptions = {}): Observable<ConfigData> {
console.log(this.halService.getEndpoint(this.linkPath));
return this.halService.getEndpoint(this.linkPath).pipe( return this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getConfigSearchHref(endpoint, options)), map((endpoint: string) => this.getConfigSearchHref(endpoint, options)),
filter((href: string) => isNotEmpty(href)), filter((href: string) => isNotEmpty(href)),
@@ -102,7 +102,7 @@ export abstract class ConfigService {
map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)), map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL)),
tap((request: RestRequest) => this.requestService.configure(request)), tap((request: RestRequest) => this.requestService.configure(request)),
mergeMap((request: RestRequest) => this.getConfig(request)), mergeMap((request: RestRequest) => this.getConfig(request)),
distinctUntilChanged(),); distinctUntilChanged());
} }
} }

View File

@@ -1,4 +1,4 @@
import { Observable, throwError as observableThrowError } from 'rxjs'; import { Observable, throwError as observableThrowError, merge as observableMerge } from 'rxjs';
import { distinctUntilChanged, filter, first, map, mergeMap, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, first, map, mergeMap, tap } from 'rxjs/operators';
import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { NormalizedCommunity } from '../cache/models/normalized-community.model';
@@ -10,6 +10,7 @@ import { DataService } from './data.service';
import { FindByIDRequest } from './request.models'; import { FindByIDRequest } from './request.models';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DSOSuccessResponse } from '../cache/response-cache.models';
export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> { export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> {
protected abstract cds: CommunityDataService; protected abstract cds: CommunityDataService;
@@ -38,23 +39,39 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
this.requestService.configure(request); this.requestService.configure(request);
}),); }),);
return scopeCommunityHrefObs.pipe( // return scopeCommunityHrefObs.pipe(
// mergeMap((href: string) => this.responseCache.get(href)),
// map((entry: ResponseCacheEntry) => entry.response),
// mergeMap((response) => {
// if (response.isSuccessful) {
// const community$: Observable<NormalizedCommunity> = this.objectCache.getByUUID(scopeID);
// return community$.pipe(
// map((community) => community._links[this.linkPath]),
// filter((href) => isNotEmpty(href)),
// distinctUntilChanged()
// );
// } else if (!response.isSuccessful) {
// return observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))
// }
// }),
// distinctUntilChanged()
// );
const responses = scopeCommunityHrefObs.pipe(
mergeMap((href: string) => this.responseCache.get(href)), mergeMap((href: string) => this.responseCache.get(href)),
map((entry: ResponseCacheEntry) => entry.response), map((entry: ResponseCacheEntry) => entry.response));
mergeMap((response) => { const errorResponses = responses.pipe(
if (response.isSuccessful) { filter((response) => !response.isSuccessful),
const community$: Observable<NormalizedCommunity> = this.objectCache.getByUUID(scopeID); mergeMap(() => observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`)))
return community$.pipe(
map((community) => community._links[this.linkPath]),
filter((href) => isNotEmpty(href)),
distinctUntilChanged()
);
} else if (!response.isSuccessful) {
return observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))
}
}),
distinctUntilChanged()
); );
const successResponses = responses.pipe(
filter((response) => response.isSuccessful),
mergeMap(() => this.objectCache.getByUUID(scopeID)),
map((nc: NormalizedCommunity) => nc._links[this.linkPath]),
filter((href) => isNotEmpty(href))
);
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged());
} }
} }
} }

View File

@@ -1,15 +1,14 @@
import { Store } from '@ngrx/store'; import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { cold, hot } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service'; import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service';
import { getMockStore } from '../../shared/mocks/mock-store';
import { defaultUUID, getMockUUIDService } from '../../shared/mocks/mock-uuid.service'; import { defaultUUID, getMockUUIDService } from '../../shared/mocks/mock-uuid.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
import { UUIDService } from '../shared/uuid.service'; import { UUIDService } from '../shared/uuid.service';
import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
import * as ngrx from '@ngrx/store';
import { import {
DeleteRequest, DeleteRequest,
GetRequest, GetRequest,
@@ -21,8 +20,12 @@ import {
RestRequest RestRequest
} from './request.models'; } from './request.models';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
import { ActionsSubject, Store } from '@ngrx/store';
import { TestScheduler } from 'rxjs/testing';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
describe('RequestService', () => { describe('RequestService', () => {
let scheduler: TestScheduler;
let service: RequestService; let service: RequestService;
let serviceAsAny: any; let serviceAsAny: any;
let objectCache: ObjectCacheService; let objectCache: ObjectCacheService;
@@ -39,8 +42,10 @@ describe('RequestService', () => {
const testOptionsRequest = new OptionsRequest(testUUID, testHref); const testOptionsRequest = new OptionsRequest(testUUID, testHref);
const testHeadRequest = new HeadRequest(testUUID, testHref); const testHeadRequest = new HeadRequest(testUUID, testHref);
const testPatchRequest = new PatchRequest(testUUID, testHref); const testPatchRequest = new PatchRequest(testUUID, testHref);
let selectSpy;
beforeEach(() => { beforeEach(() => {
scheduler = getTestScheduler();
objectCache = getMockObjectCacheService(); objectCache = getMockObjectCacheService();
(objectCache.hasBySelfLink as any).and.returnValue(false); (objectCache.hasBySelfLink as any).and.returnValue(false);
@@ -50,8 +55,13 @@ describe('RequestService', () => {
uuidService = getMockUUIDService(); uuidService = getMockUUIDService();
store = getMockStore<CoreState>(); store = new Store<CoreState>(new BehaviorSubject({}), new ActionsSubject(), null);
(store.pipe as any).and.returnValue(observableOf(undefined)); selectSpy = spyOnProperty(ngrx, 'select')
selectSpy.and.callFake(() => {
return () => {
return () => cold('a', { a: undefined });
};
});
service = new RequestService( service = new RequestService(
objectCache, objectCache,
@@ -134,11 +144,15 @@ describe('RequestService', () => {
describe('getByUUID', () => { describe('getByUUID', () => {
describe('if the request with the specified UUID exists in the store', () => { describe('if the request with the specified UUID exists in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.pipe as any).and.returnValues(hot('a', { selectSpy.and.callFake(() => {
a: { return () => {
completed: true return () => hot('a', {
} a: {
})); completed: true
}
});
};
});
}); });
it('should return an Observable of the RequestEntry', () => { it('should return an Observable of the RequestEntry', () => {
@@ -155,9 +169,11 @@ describe('RequestService', () => {
describe('if the request with the specified UUID doesn\'t exist in the store', () => { describe('if the request with the specified UUID doesn\'t exist in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.pipe as any).and.returnValues(hot('a', { selectSpy.and.callFake(() => {
a: undefined return () => {
})); return () => hot('a', { a: undefined });
};
});
}); });
it('should return an Observable of undefined', () => { it('should return an Observable of undefined', () => {
@@ -175,9 +191,11 @@ describe('RequestService', () => {
describe('getByHref', () => { describe('getByHref', () => {
describe('when the request with the specified href exists in the store', () => { describe('when the request with the specified href exists in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.pipe as any).and.returnValues(hot('a', { selectSpy.and.callFake(() => {
a: testUUID return () => {
})); return () => hot('a', { a: testUUID });
};
});
spyOn(service, 'getByUUID').and.returnValue(cold('b', { spyOn(service, 'getByUUID').and.returnValue(cold('b', {
b: { b: {
completed: true completed: true
@@ -199,9 +217,11 @@ describe('RequestService', () => {
describe('when the request with the specified href doesn\'t exist in the store', () => { describe('when the request with the specified href doesn\'t exist in the store', () => {
beforeEach(() => { beforeEach(() => {
(store.pipe as any).and.returnValues(hot('a', { selectSpy.and.callFake(() => {
a: undefined return () => {
})); return () => hot('a', { a: undefined });
};
});
spyOn(service, 'getByUUID').and.returnValue(cold('b', { spyOn(service, 'getByUUID').and.returnValue(cold('b', {
b: undefined b: undefined
})); }));
@@ -241,7 +261,8 @@ describe('RequestService', () => {
}); });
it('should dispatch the request', () => { it('should dispatch the request', () => {
service.configure(request); scheduler.schedule(() => service.configure(request));
scheduler.flush();
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(request); expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(request);
}); });
}); });
@@ -398,6 +419,10 @@ describe('RequestService', () => {
}); });
describe('dispatchRequest', () => { describe('dispatchRequest', () => {
beforeEach(() => {
spyOn(store, 'dispatch');
});
it('should dispatch a RequestConfigureAction', () => { it('should dispatch a RequestConfigureAction', () => {
const request = testGetRequest; const request = testGetRequest;
serviceAsAny.dispatchRequest(request); serviceAsAny.dispatchRequest(request);
@@ -428,7 +453,11 @@ describe('RequestService', () => {
describe('when the request is added to the store', () => { describe('when the request is added to the store', () => {
it('should stop tracking the request', () => { it('should stop tracking the request', () => {
(store.pipe as any).and.returnValues(observableOf({ request })); selectSpy.and.callFake(() => {
return () => {
return () => observableOf({ request });
};
});
serviceAsAny.trackRequestsOnTheirWayToTheStore(request); serviceAsAny.trackRequestsOnTheirWayToTheStore(request);
expect(serviceAsAny.requestsOnTheirWayToTheStore.includes(request.href)).toBeFalsy(); expect(serviceAsAny.requestsOnTheirWayToTheStore.includes(request.href)).toBeFalsy();
}); });

View File

@@ -1,12 +1,12 @@
import { Observable } from 'rxjs'; import { Observable, merge as observableMerge } from 'rxjs';
import { filter, first, map, mergeMap, take } from 'rxjs/operators'; import { filter, first, map, mergeMap, partition, take } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MemoizedSelector, select, Store } from '@ngrx/store'; import { MemoizedSelector, select, Store } from '@ngrx/store';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { DSOSuccessResponse } from '../cache/response-cache.models'; import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
import { coreSelector, CoreState } from '../core.reducers'; import { coreSelector, CoreState } from '../core.reducers';
@@ -82,17 +82,21 @@ export class RequestService {
private isCachedOrPending(request: GetRequest) { private isCachedOrPending(request: GetRequest) {
let isCached = this.objectCache.hasBySelfLink(request.href); let isCached = this.objectCache.hasBySelfLink(request.href);
if (!isCached && this.responseCache.has(request.href)) { if (!isCached && this.responseCache.has(request.href)) {
this.responseCache.get(request.href).pipe( const responses = this.responseCache.get(request.href).pipe(
first(), take(1),
map((entry: ResponseCacheEntry) => { map((entry: ResponseCacheEntry) => entry.response)
const response = entry.response; );
if (response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)) {
return (response as DSOSuccessResponse).resourceSelfLinks.every((selfLink) => this.objectCache.hasBySelfLink(selfLink)) const errorResponses = responses.pipe(filter((response) => !response.isSuccessful), map(() => true)); // TODO add a configurable number of retries in case of an error.
} else { const dsoSuccessResponses = responses.pipe(
return true; filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)),
} map((response: DSOSuccessResponse) => response.resourceSelfLinks),
}) map((resourceSelfLinks: string[]) => resourceSelfLinks
).subscribe((c) => isCached = c); .every((selfLink) => this.objectCache.hasBySelfLink(selfLink))
));
const otherSuccessResponses = responses.pipe(filter((response) => response.isSuccessful && !hasValue((response as DSOSuccessResponse).resourceSelfLinks)), map(() => true));
observableMerge(errorResponses, otherSuccessResponses, dsoSuccessResponses).subscribe((c) => isCached = c);
} }
const isPending = this.isPending(request); const isPending = this.isPending(request);
return isCached || isPending; return isCached || isPending;

View File

@@ -0,0 +1,37 @@
import { select } from '@ngrx/store';
import * as ngrx from '@ngrx/store';
import { cold, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs';
import { count, take } from 'rxjs/operators';
class TestClass {
selectSomething(input$: Observable<any>) {
return input$.pipe(
select('something'),
take(1)
)
}
}
describe('mockSelect', () => {
let testClass;
beforeEach(() => {
spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => cold('a', { a: 'bingo!' });
};
});
testClass = new TestClass();
});
it('should mock select', () => {
const input$ = hot('a', { a: '' });
const expected$ = hot('(b|)', { b: 'bingo!' });
const result$ = testClass.selectSomething(input$);
result$.pipe(count()).subscribe((t) => console.log('resykts', t));
expected$.pipe(count()).subscribe((t) => console.log('expected', t));
result$.subscribe((v) => console.log('result$', v));
expected$.subscribe((v) => console.log('expected$', v));
expect(result$).toBeObservable(expected$)
});
})

View File

@@ -19,7 +19,7 @@ import { HostWindowServiceStub } from '../shared/testing/host-window-service-stu
import { RouterStub } from '../shared/testing/router-stub'; import { RouterStub } from '../shared/testing/router-stub';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import * as ngrx from '@ngrx/store';
let comp: HeaderComponent; let comp: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>; let fixture: ComponentFixture<HeaderComponent>;
let store: Store<HeaderState>; let store: Store<HeaderState>;
@@ -72,7 +72,11 @@ describe('HeaderComponent', () => {
beforeEach(() => { beforeEach(() => {
menu = fixture.debugElement.query(By.css('#collapsingNav')).nativeElement; menu = fixture.debugElement.query(By.css('#collapsingNav')).nativeElement;
spyOn(store, 'select').and.returnValue(observableOf({ navCollapsed: true })); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf({ navCollapsed: true })
};
});
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -87,7 +91,11 @@ describe('HeaderComponent', () => {
beforeEach(() => { beforeEach(() => {
menu = fixture.debugElement.query(By.css('#collapsingNav')).nativeElement; menu = fixture.debugElement.query(By.css('#collapsingNav')).nativeElement;
spyOn(store, 'select').and.returnValue(observableOf(false)); spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => observableOf(false)
};
});
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,7 +1,6 @@
// Load the implementations that should be tested // Load the implementations that should be tested
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing'; import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing';
import 'rxjs/add/observable/of';
import { Chips } from './models/chips.model'; import { Chips } from './models/chips.model';
import { UploaderService } from '../uploader/uploader.service'; import { UploaderService } from '../uploader/uploader.service';

View File

@@ -12,429 +12,7 @@
<div [ngClass]="{'form-row': model.hasLanguages }"> <div [ngClass]="{'form-row': model.hasLanguages }">
<div [ngClass]="getClass('grid', 'control')"> <div [ngClass]="getClass('grid', 'control')">
<ng-container [ngSwitch]="type"> <ng-container #componentViewContainer></ng-container>
<!-- FORM ARRAY ------------------------------------------------------------------------------------------->
<div *ngSwitchCase="1"
[dynamicId]="bindId && model.id"
[formArrayName]="model.id"
[ngClass]="getClass('element', 'control')">
<div *ngFor="let groupModel of model.groups; let idx = index" role="group"
[formGroupName]="idx" [ngClass]="[getClass('element', 'group'), getClass('grid', 'group')]">
<ds-dynamic-form-control *ngFor="let _model of groupModel.group"
[bindId]="false"
[formId]="formId"
[context]="groupModel"
[group]="control.at(idx)"
[hasErrorMessaging]="_model.hasErrorMessages"
[hidden]="_model.hidden"
[layout]="layout"
[model]="_model"
[templates]="templateList"
[ngClass]="[getClass('element', 'host', _model), getClass('grid', 'host', _model)]"
(dfBlur)="onBlur($event)"
(dfChange)="onValueChange($event)"
(dfFocus)="onFocus($event)"></ds-dynamic-form-control>
<ng-container *ngTemplateOutlet="templates[2]?.templateRef; context: groupModel"></ng-container>
</div>
</div>
<!-- CALENDAR --------------------------------------------------------------------------------------------->
<ngb-datepicker *ngSwitchCase="2"
[displayMonths]="getAdditional('displayMonths', 1)"
[dynamicId]="bindId && model.id"
[firstDayOfWeek]="getAdditional('firstDayOfWeek', 1)"
[formControlName]="model.id"
[maxDate]="model.max"
[minDate]="model.min"
[navigation]="getAdditional('navigation', 'select')"
[ngClass]="getClass('element', 'control')"
[outsideDays]="getAdditional('outsideDays', 'visible')"
[showWeekdays]="getAdditional('showWeekdays', true)"
[showWeekNumbers]="getAdditional('showWeekNumbers', false)"
[startDate]="model.focusedDate"
(select)="onValueChange($event)"></ngb-datepicker>
<!-- CHECKBOX --------------------------------------------------------------------------------------------->
<div *ngSwitchCase="3" class="custom-control custom-checkbox" [class.disabled]="model.disabled">
<input type="checkbox" class="custom-control-input"
[checked]="model.checked"
[class.is-invalid]="showErrorMessages"
[id]="bindId && model.id"
[dynamicId]="bindId && model.id"
[formControlName]="model.id"
[indeterminate]="model.indeterminate"
[name]="model.name"
[ngClass]="getClass('element', 'control')"
[required]="model.required"
[tabindex]="model.tabIndex"
[value]="model.value"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)" >
<label class="custom-control-label" [for]="bindId && model.id">
<span [innerHTML]="model.label"
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]">
</span>
</label>
</div>
<!-- CHECKBOX GROUP --------------------------------------------------------------------------------------->
<div *ngSwitchCase="4" class="btn-group btn-group-toggle" data-toggle="buttons"
[dynamicId]="bindId && model.id"
[formGroupName]="model.id"
[ngClass]="getClass('element', 'control')">
<label *ngFor="let checkboxModel of model.group" ngbButtonLabel
[hidden]="checkboxModel.hidden"
[ngClass]="getClass('element', 'control', checkboxModel)">
<input type="checkbox" ngbButton
[checked]="checkboxModel.checked"
[id]="bindId && checkboxModel.id"
[dynamicId]="bindId && checkboxModel.id"
[formControlName]="checkboxModel.id"
[indeterminate]="checkboxModel.indeterminate"
[name]="checkboxModel.name"
[required]="checkboxModel.required"
[tabindex]="checkboxModel.tabIndex"
[value]="checkboxModel.value"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"/>
<span [ngClass]="getClass('element', 'label', checkboxModel)"
[innerHTML]="checkboxModel.label"></span></label>
</div>
<!-- DATEPICKER ------------------------------------------------------------------------------------------->
<div *ngSwitchCase="5" class="input-group">
<input ngbDatepicker class="form-control" #datepicker="ngbDatepicker"
[class.is-invalid]="showErrorMessages"
[displayMonths]="getAdditional('displayMonths', 1)"
[dynamicId]="bindId && model.id"
[firstDayOfWeek]="getAdditional('firstDayOfWeek', 1)"
[formControlName]="model.id"
[maxDate]="model.max"
[minDate]="model.min"
[name]="model.name"
[navigation]="getAdditional('navigation', 'select')"
[ngClass]="getClass('element', 'control')"
[outsideDays]="getAdditional('outsideDays', 'visible')"
[placeholder]="(model.placeholder | translate)"
[placement]="getAdditional('placement', 'bottom-left')"
[showWeekdays]="getAdditional('showWeekdays', true)"
[showWeekNumbers]="getAdditional('showWeekNumbers', false)"
[startDate]="model.focusedDate"
(dateSelect)="onValueChange($event)"
(blur)="onBlur($event)"
(focus)="onFocus($event)">
<div class="input-group-append">
<button class="btn btn-outline-secondary"
type="button"
[class.disabled]="model.disabled"
[disabled]="model.disabled"
(click)="datepicker.toggle()">
<i *ngIf="model.toggleIcon" class="{{model.toggleIcon}}" aria-hidden="true"></i>
<span *ngIf="model.toggleLabel">{{ model.toggleLabel }}</span>
</button>
</div>
</div>
<!-- FORM GROUP ------------------------------------------------------------------------------------------->
<div *ngSwitchCase="6" role="group"
[dynamicId]="bindId && model.id"
[formGroupName]="model.id"
[ngClass]="getClass('element','control')">
<ds-dynamic-form-control *ngFor="let _model of model.group"
[asBootstrapFormGroup]="true"
[formId]="formId"
[group]="control"
[hasErrorMessaging]="_model.hasErrorMessages"
[hidden]="_model.hidden"
[layout]="layout"
[model]="_model"
[templates]="templateList"
[ngClass]="[getClass('element', 'host', _model), getClass('grid', 'host', _model)]"
(dfBlur)="onBlur($event)"
(dfChange)="onValueChange($event)"
(dfFocus)="onFocus($event)"></ds-dynamic-form-control>
</div>
<!-- INPUT ------------------------------------------------------------------------------------------------>
<div *ngSwitchCase="7" [class.input-group]="model.prefix || model.suffix">
<div *ngIf="model.prefix" class="input-group-prepend">
<span class="input-group-text" [innerHTML]="model.prefix"></span>
</div>
<ng-container *ngTemplateOutlet="inputTemplate;
context:{bindId: bindId, model: model, showErrorMessages: showErrorMessages}">
</ng-container>
<div *ngIf="model.suffix" class="input-group-append">
<span class="input-group-text" [innerHTML]="model.suffix"></span>
</div>
<datalist *ngIf="model.list" [id]="model.listId">
<option *ngFor="let option of model.list" [value]="option">
</datalist>
</div>
<!-- RADIO GROUP ------------------------------------------------------------------------------------------>
<div *ngSwitchCase="8" ngbRadioGroup class="btn-group btn-group-toggle" role="radiogroup"
[dynamicId]="bindId && model.id"
[formControlName]="model.id"
[ngClass]="getClass('element', 'control')"
[tabindex]="model.tabIndex"
(change)="onValueChange($event)">
<legend *ngIf="model.legend" [innerHTML]="model.legend"></legend>
<label *ngFor="let option of model.options$ | async" ngbButtonLabel
[ngClass]="[getClass('element', 'option'), getClass('grid', 'option')]">
<input type="radio" ngbButton
[disabled]="option.disabled"
[name]="model.name"
[value]="option.value"
(blur)="onBlur($event)"
(focus)="onFocus($event)"/><span [innerHTML]="option.label"></span>
</label>
</div>
<!-- SELECT ----------------------------------------------------------------------------------------------->
<ng-container *ngSwitchCase="9">
<ng-container *ngTemplateOutlet="selectTemplate;
context:{bindId: bindId, model: model, showErrorMessages: showErrorMessages}">
</ng-container>
</ng-container>
<!-- TEXTAREA --------------------------------------------------------------------------------------------->
<textarea *ngSwitchCase="10" class="form-control"
[class.is-invalid]="showErrorMessages"
[dynamicId]="bindId && model.id"
[cols]="model.cols"
[formControlName]="model.id"
[maxlength]="model.maxLength"
[minlength]="model.minLength"
[name]="model.name"
[ngClass]="getClass('element', 'control')"
[placeholder]="(model.placeholder | translate)"
[readonly]="model.readOnly"
[required]="model.required"
[rows]="model.rows"
[spellcheck]="model.spellCheck"
[tabindex]="model.tabIndex"
[wrap]="model.wrap"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"></textarea>
<!-- TIMEPICKER ------------------------------------------------------------------------------------------->
<ngb-timepicker *ngSwitchCase="11"
[dynamicId]="bindId && model.id"
[formControlName]="model.id"
[hourStep]="getAdditional('hourStep', 1)"
[meridian]="model.meridian"
[minuteStep]="getAdditional('minuteStep', 1)"
[ngClass]="getClass('element', 'control')"
[seconds]="model.showSeconds"
[secondStep]="getAdditional('secondStep', 1)"
[size]="getAdditional('size', 'medium')"
[spinners]="getAdditional('spinners', true)"></ngb-timepicker>
<ng-container *ngSwitchCase="12">
<ng-container *ngTemplateOutlet="typeaheadTemplate;
context:{bindId: bindId, model: model, showErrorMessages: showErrorMessages}">
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="13">
<ng-container *ngTemplateOutlet="scrollableDropdownTemplate;
context:{bindId: bindId, model: model, showErrorMessages: showErrorMessages}">
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="14">
<ng-container *ngTemplateOutlet="tagTemplate;
context:{bindId: bindId, model: model, showErrorMessages: showErrorMessages}">
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="15">
<ng-container *ngTemplateOutlet="listTemplate;
context:{bindId: bindId, model: model, showErrorMessages: showErrorMessages}">
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="16">
<ds-dynamic-group [model]="model"
[formId]="formId"
[group]="group"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-group>
</ng-container>
<ng-container *ngSwitchCase="17">
<ds-date-picker
[bindId]="bindId"
[group]="group"
[model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-date-picker>
</ng-container>
<ng-container *ngSwitchCase="18">
<ds-dynamic-lookup
[bindId]="bindId"
[group]="group"
[model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"
></ds-dynamic-lookup>
</ng-container>
<ng-container *ngSwitchCase="19">
<ds-dynamic-lookup
[bindId]="bindId"
[group]="group"
[model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"
></ds-dynamic-lookup>
</ng-container>
<small *ngIf="model.hint" class="text-muted" [innerHTML]="model.hint"
[ngClass]="getClass('element', 'hint')"></small>
<div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
<small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate:model.validators }}</small>
</div>
</ng-container>
<ng-template #inputTemplate let-bindId="bindId" let-model="model"
let-showErrorMessages="showErrorMessages">
<input [attr.accept]="model.accept"
[attr.list]="model.listId"
[attr.max]="model.max"
[attr.min]="model.min"
[attr.multiple]="model.multiple"
[attr.step]="model.step"
[autocomplete]="model.autoComplete"
[autofocus]="model.autoFocus"
[class.form-control]="model.inputType !== 'file'"
[class.form-control-file]="model.inputType === 'file'"
[class.is-invalid]="showErrorMessages"
[dynamicId]="bindId && model.id"
[formControlName]="model.id"
[maxlength]="model.maxLength"
[minlength]="model.minLength"
[name]="model.name"
[ngClass]="getClass('element', 'control')"
[pattern]="model.pattern"
[placeholder]="(model.placeholder | translate)"
[readonly]="model.readOnly"
[required]="model.required"
[spellcheck]="model.spellCheck"
[tabindex]="model.tabIndex"
[textMask]="{mask: (model.mask || false), showMask: model.mask && !(model.placeholder)}"
[type]="model.inputType"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"/>
</ng-template>
<ng-template #selectTemplate let-bindId="bindId" let-model="model"
let-showErrorMessages="showErrorMessages">
<select class="form-control"
[class.is-invalid]="showErrorMessages"
[dynamicId]="bindId && model.id"
[formControlName]="model.id"
[name]="model.name"
[ngClass]="getClass('element', 'control')"
[required]="model.required"
[tabindex]="model.tabIndex"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)">
<option *ngFor="let option of model.options$ | async"
[disabled]="option.disabled"
[ngValue]="option.value">{{ option.label }}</option>
</select>
</ng-template>
<ng-template #typeaheadTemplate let-bindId="bindId" let-model="model"
let-showErrorMessages="showErrorMessages">
<ds-dynamic-typeahead [bindId]="bindId"
[group]="group"
[model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-typeahead>
</ng-template>
<ng-template #scrollableDropdownTemplate let-bindId="bindId" let-model="model"
let-showErrorMessages="showErrorMessages">
<ds-dynamic-scrollable-dropdown [bindId]="bindId"
[group]="group"
[model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-scrollable-dropdown>
</ng-template>
<ng-template #tagTemplate let-bindId="bindId" let-model="model"
let-showErrorMessages="showErrorMessages">
<ds-dynamic-tag [bindId]="bindId"
[group]="group"
[model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-tag>
</ng-template>
<ng-template #listTemplate let-bindId="bindId" let-model="model"
let-showErrorMessages="showErrorMessages">
<ds-dynamic-list [bindId]="bindId"
[group]="group"
[model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)"
(change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-list>
</ng-template>
</div> </div>

View File

@@ -25,7 +25,7 @@ import {
DynamicTextAreaModel, DynamicTextAreaModel,
DynamicTimePickerModel DynamicTimePickerModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { DsDynamicFormControlComponent, NGBootstrapFormControlType } from './ds-dynamic-form-control.component'; import { DsDynamicFormControlComponent } from './ds-dynamic-form-control.component';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '../../../shared.module'; import { SharedModule } from '../../../shared.module';
import { DynamicDsDatePickerModel } from './models/date-picker/date-picker.model'; import { DynamicDsDatePickerModel } from './models/date-picker/date-picker.model';
@@ -39,6 +39,27 @@ import { DynamicTagModel } from './models/tag/dynamic-tag.model';
import { DynamicTypeaheadModel } from './models/typeahead/dynamic-typeahead.model'; import { DynamicTypeaheadModel } from './models/typeahead/dynamic-typeahead.model';
import { DynamicQualdropModel } from './models/ds-dynamic-qualdrop.model'; import { DynamicQualdropModel } from './models/ds-dynamic-qualdrop.model';
import { DynamicLookupNameModel } from './models/lookup/dynamic-lookup-name.model'; import { DynamicLookupNameModel } from './models/lookup/dynamic-lookup-name.model';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import {
DynamicNGBootstrapCalendarComponent,
DynamicNGBootstrapCheckboxComponent,
DynamicNGBootstrapCheckboxGroupComponent,
DynamicNGBootstrapDatePickerComponent,
DynamicNGBootstrapFormArrayComponent,
DynamicNGBootstrapFormGroupComponent,
DynamicNGBootstrapInputComponent,
DynamicNGBootstrapRadioGroupComponent,
DynamicNGBootstrapSelectComponent,
DynamicNGBootstrapTextAreaComponent,
DynamicNGBootstrapTimePickerComponent
} from '@ng-dynamic-forms/ui-ng-bootstrap';
import { DsDynamicTypeaheadComponent } from './models/typeahead/dynamic-typeahead.component';
import { DsDynamicScrollableDropdownComponent } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
import { DsDynamicTagComponent } from './models/tag/dynamic-tag.component';
import { DsDynamicListComponent } from './models/list/dynamic-list.component';
import { DsDynamicGroupComponent } from './models/dynamic-group/dynamic-group.components';
import { DsDatePickerComponent } from './models/date-picker/date-picker.component';
import { DsDynamicLookupComponent } from './models/lookup/dynamic-lookup.component';
describe('DsDynamicFormControlComponent test suite', () => { describe('DsDynamicFormControlComponent test suite', () => {
@@ -49,27 +70,42 @@ describe('DsDynamicFormControlComponent test suite', () => {
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23' scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
}; };
const formModel = [ const formModel = [
new DynamicCheckboxModel({id: 'checkbox'}), new DynamicCheckboxModel({ id: 'checkbox' }),
new DynamicCheckboxGroupModel({id: 'checkboxGroup', group: []}), new DynamicCheckboxGroupModel({ id: 'checkboxGroup', group: [] }),
new DynamicColorPickerModel({id: 'colorpicker'}), new DynamicColorPickerModel({ id: 'colorpicker' }),
new DynamicDatePickerModel({id: 'datepicker'}), new DynamicDatePickerModel({ id: 'datepicker' }),
new DynamicEditorModel({id: 'editor'}), new DynamicEditorModel({ id: 'editor' }),
new DynamicFileUploadModel({id: 'upload', url: ''}), new DynamicFileUploadModel({ id: 'upload', url: '' }),
new DynamicFormArrayModel({id: 'formArray', groupFactory: () => []}), new DynamicFormArrayModel({ id: 'formArray', groupFactory: () => [] }),
new DynamicFormGroupModel({id: 'formGroup', group: []}), new DynamicFormGroupModel({ id: 'formGroup', group: [] }),
new DynamicInputModel({id: 'input', maxLength: 51}), new DynamicInputModel({ id: 'input', maxLength: 51 }),
new DynamicRadioGroupModel({id: 'radioGroup'}), new DynamicRadioGroupModel({ id: 'radioGroup' }),
new DynamicRatingModel({id: 'rating'}), new DynamicRatingModel({ id: 'rating' }),
new DynamicSelectModel({id: 'select', options: [{value: 'One'}, {value: 'Two'}], value: 'One'}), new DynamicSelectModel({
new DynamicSliderModel({id: 'slider'}), id: 'select',
new DynamicSwitchModel({id: 'switch'}), options: [{ value: 'One' }, { value: 'Two' }],
new DynamicTextAreaModel({id: 'textarea'}), value: 'One'
new DynamicTimePickerModel({id: 'timepicker'}), }),
new DynamicTypeaheadModel({id: 'typeahead'}), new DynamicSliderModel({ id: 'slider' }),
new DynamicScrollableDropdownModel({id: 'scrollableDropdown', authorityOptions: authorityOptions}), new DynamicSwitchModel({ id: 'switch' }),
new DynamicTagModel({id: 'tag'}), new DynamicTextAreaModel({ id: 'textarea' }),
new DynamicListCheckboxGroupModel({id: 'checkboxList', authorityOptions: authorityOptions, repeatable: true}), new DynamicTimePickerModel({ id: 'timepicker' }),
new DynamicListRadioGroupModel({id: 'radioList', authorityOptions: authorityOptions, repeatable: false}), new DynamicTypeaheadModel({ id: 'typeahead' }),
new DynamicScrollableDropdownModel({
id: 'scrollableDropdown',
authorityOptions: authorityOptions
}),
new DynamicTagModel({ id: 'tag' }),
new DynamicListCheckboxGroupModel({
id: 'checkboxList',
authorityOptions: authorityOptions,
repeatable: true
}),
new DynamicListRadioGroupModel({
id: 'radioList',
authorityOptions: authorityOptions,
repeatable: false
}),
new DynamicGroupModel({ new DynamicGroupModel({
id: 'relationGroup', id: 'relationGroup',
formConfiguration: [], formConfiguration: [],
@@ -79,10 +115,10 @@ describe('DsDynamicFormControlComponent test suite', () => {
scopeUUID: '', scopeUUID: '',
submissionScope: '' submissionScope: ''
}), }),
new DynamicDsDatePickerModel({id: 'datepicker'}), new DynamicDsDatePickerModel({ id: 'datepicker' }),
new DynamicLookupModel({id: 'lookup'}), new DynamicLookupModel({ id: 'lookup' }),
new DynamicLookupNameModel({id: 'lookupName'}), new DynamicLookupNameModel({ id: 'lookupName' }),
new DynamicQualdropModel({id: 'combobox', readOnly: false}) new DynamicQualdropModel({ id: 'combobox', readOnly: false })
]; ];
const testModel = formModel[8]; const testModel = formModel[8];
let formGroup: FormGroup; let formGroup: FormGroup;
@@ -93,6 +129,13 @@ describe('DsDynamicFormControlComponent test suite', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [DynamicNGBootstrapInputComponent]
}
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
@@ -102,8 +145,9 @@ describe('DsDynamicFormControlComponent test suite', () => {
DynamicFormsCoreModule.forRoot(), DynamicFormsCoreModule.forRoot(),
SharedModule, SharedModule,
TranslateModule.forRoot(), TranslateModule.forRoot(),
TextMaskModule, TextMaskModule
], ],
providers: [DsDynamicFormControlComponent, DynamicFormService],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents().then(() => { }).compileComponents().then(() => {
@@ -128,12 +172,11 @@ describe('DsDynamicFormControlComponent test suite', () => {
}); });
fixture.detectChanges(); fixture.detectChanges();
console.log(fixture.componentInstance.componentViewContainerRef);
testElement = debugElement.query(By.css(`input[id='${testModel.id}']`)); testElement = debugElement.query(By.css(`input[id='${testModel.id}']`));
})); }));
it('should initialize correctly', () => { it('should initialize correctly', () => {
expect(component.context).toBeNull(); expect(component.context).toBeNull();
expect(component.control instanceof FormControl).toBe(true); expect(component.control instanceof FormControl).toBe(true);
expect(component.group instanceof FormGroup).toBe(true); expect(component.group instanceof FormGroup).toBe(true);
@@ -149,15 +192,7 @@ describe('DsDynamicFormControlComponent test suite', () => {
expect(component.change).toBeDefined(); expect(component.change).toBeDefined();
expect(component.focus).toBeDefined(); expect(component.focus).toBeDefined();
expect(component.onChange).toBeDefined(); expect(component.componentType).toBe(DynamicNGBootstrapInputComponent);
expect(component.onBlur).toBeDefined();
expect(component.onFocus).toBeDefined();
expect(component.isValid).toBe(true);
expect(component.isInvalid).toBe(false);
expect(component.showErrorMessages).toBe(false);
expect(component.type).toBe(NGBootstrapFormControlType.Input);
}); });
it('should have an input element', () => { it('should have an input element', () => {
@@ -219,63 +254,36 @@ describe('DsDynamicFormControlComponent test suite', () => {
expect(component.onModelDisabledUpdates).toHaveBeenCalled(); expect(component.onModelDisabledUpdates).toHaveBeenCalled();
}); });
it('should determine correct form control type', () => { it('should map a form control model to a form control component', () => {
const testFn = DsDynamicFormControlComponent.getFormControlType; const testFn = DsDynamicFormControlComponent.getFormControlType;
expect(testFn(formModel[0])).toBe(DynamicNGBootstrapCheckboxComponent);
expect(testFn(formModel[0])).toEqual(NGBootstrapFormControlType.Checkbox); expect(testFn(formModel[1])).toBe(DynamicNGBootstrapCheckboxGroupComponent);
expect(testFn(formModel[1])).toEqual(NGBootstrapFormControlType.CheckboxGroup);
expect(testFn(formModel[2])).toBeNull(); expect(testFn(formModel[2])).toBeNull();
expect(testFn(formModel[3])).toBe(DynamicNGBootstrapDatePickerComponent);
expect(testFn(formModel[3])).toEqual(NGBootstrapFormControlType.DatePicker);
(formModel[3] as DynamicDatePickerModel).inline = true; (formModel[3] as DynamicDatePickerModel).inline = true;
expect(testFn(formModel[3])).toEqual(NGBootstrapFormControlType.Calendar); expect(testFn(formModel[3])).toBe(DynamicNGBootstrapCalendarComponent);
expect(testFn(formModel[4])).toBeNull(); expect(testFn(formModel[4])).toBeNull();
expect(testFn(formModel[5])).toBeNull(); expect(testFn(formModel[5])).toBeNull();
expect(testFn(formModel[6])).toBe(DynamicNGBootstrapFormArrayComponent);
expect(testFn(formModel[6])).toEqual(NGBootstrapFormControlType.Array); expect(testFn(formModel[7])).toBe(DynamicNGBootstrapFormGroupComponent);
expect(testFn(formModel[8])).toBe(DynamicNGBootstrapInputComponent);
expect(testFn(formModel[7])).toEqual(NGBootstrapFormControlType.Group); expect(testFn(formModel[9])).toBe(DynamicNGBootstrapRadioGroupComponent);
expect(testFn(formModel[8])).toEqual(NGBootstrapFormControlType.Input);
expect(testFn(formModel[9])).toEqual(NGBootstrapFormControlType.RadioGroup);
expect(testFn(formModel[10])).toBeNull(); expect(testFn(formModel[10])).toBeNull();
expect(testFn(formModel[11])).toBe(DynamicNGBootstrapSelectComponent);
expect(testFn(formModel[11])).toEqual(NGBootstrapFormControlType.Select);
expect(testFn(formModel[12])).toBeNull(); expect(testFn(formModel[12])).toBeNull();
expect(testFn(formModel[13])).toBeNull(); expect(testFn(formModel[13])).toBeNull();
expect(testFn(formModel[14])).toBe(DynamicNGBootstrapTextAreaComponent);
expect(testFn(formModel[14])).toEqual(NGBootstrapFormControlType.TextArea); expect(testFn(formModel[15])).toBe(DynamicNGBootstrapTimePickerComponent);
expect(testFn(formModel[16])).toBe(DsDynamicTypeaheadComponent);
expect(testFn(formModel[15])).toEqual(NGBootstrapFormControlType.TimePicker); expect(testFn(formModel[17])).toBe(DsDynamicScrollableDropdownComponent);
expect(testFn(formModel[18])).toBe(DsDynamicTagComponent);
expect(testFn(formModel[16])).toEqual(NGBootstrapFormControlType.TypeAhead); expect(testFn(formModel[19])).toBe(DsDynamicListComponent);
expect(testFn(formModel[20])).toBe(DsDynamicListComponent);
expect(testFn(formModel[17])).toEqual(NGBootstrapFormControlType.ScrollableDropdown); expect(testFn(formModel[21])).toBe(DsDynamicGroupComponent);
expect(testFn(formModel[22])).toBe(DsDatePickerComponent);
expect(testFn(formModel[18])).toEqual(NGBootstrapFormControlType.Tag); expect(testFn(formModel[23])).toBe(DsDynamicLookupComponent);
expect(testFn(formModel[24])).toBe(DsDynamicLookupComponent);
expect(testFn(formModel[19])).toEqual(NGBootstrapFormControlType.List); expect(testFn(formModel[25])).toBe(DynamicNGBootstrapFormGroupComponent);
expect(testFn(formModel[20])).toEqual(NGBootstrapFormControlType.List);
expect(testFn(formModel[21])).toEqual(NGBootstrapFormControlType.Relation);
expect(testFn(formModel[22])).toEqual(NGBootstrapFormControlType.Date);
expect(testFn(formModel[23])).toEqual(NGBootstrapFormControlType.Lookup);
expect(testFn(formModel[24])).toEqual(NGBootstrapFormControlType.LookupName);
expect(testFn(formModel[25])).toEqual(NGBootstrapFormControlType.Group);
}); });
}); });

View File

@@ -6,7 +6,7 @@ import {
OnChanges, OnChanges,
Output, Output,
QueryList, QueryList,
SimpleChanges, Type SimpleChanges, Type, ViewChild, ViewContainerRef
} from '@angular/core'; } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { import {
@@ -39,29 +39,26 @@ import { DynamicListCheckboxGroupModel } from './models/list/dynamic-list-checkb
import { DynamicListRadioGroupModel } from './models/list/dynamic-list-radio-group.model'; import { DynamicListRadioGroupModel } from './models/list/dynamic-list-radio-group.model';
import { isNotEmpty } from '../../../empty.util'; import { isNotEmpty } from '../../../empty.util';
import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME } from './models/lookup/dynamic-lookup-name.model'; import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME } from './models/lookup/dynamic-lookup-name.model';
import { DsDynamicTagComponent } from './models/tag/dynamic-tag.component';
export const enum NGBootstrapFormControlType { import {
DynamicNGBootstrapCalendarComponent,
Array = 1, // 'ARRAY', DynamicNGBootstrapCheckboxComponent,
Calendar = 2, // 'CALENDAR', DynamicNGBootstrapCheckboxGroupComponent,
Checkbox = 3, // 'CHECKBOX', DynamicNGBootstrapDatePickerComponent,
CheckboxGroup = 4, // 'CHECKBOX_GROUP', DynamicNGBootstrapFormArrayComponent,
DatePicker = 5, // 'DATEPICKER', DynamicNGBootstrapFormGroupComponent,
Group = 6, // 'GROUP', DynamicNGBootstrapInputComponent,
Input = 7, // 'INPUT', DynamicNGBootstrapRadioGroupComponent,
RadioGroup = 8, // 'RADIO_GROUP', DynamicNGBootstrapSelectComponent,
Select = 9, // 'SELECT', DynamicNGBootstrapTextAreaComponent,
TextArea = 10, // 'TEXTAREA', DynamicNGBootstrapTimePickerComponent
TimePicker = 11, // 'TIMEPICKER' } from '@ng-dynamic-forms/ui-ng-bootstrap';
TypeAhead = 12, // 'TYPEAHEAD' import { DsDatePickerComponent } from './models/date-picker/date-picker.component';
ScrollableDropdown = 13, // 'SCROLLABLE_DROPDOWN' import { DsDynamicListComponent } from './models/list/dynamic-list.component';
Tag = 14, // 'TAG' import { DsDynamicTypeaheadComponent } from './models/typeahead/dynamic-typeahead.component';
List = 15, // 'TYPELIST' import { DsDynamicScrollableDropdownComponent } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
Relation = 16, // 'RELATION' import { DsDynamicGroupComponent } from './models/dynamic-group/dynamic-group.components';
Date = 17, // 'DATE' import { DsDynamicLookupComponent } from './models/lookup/dynamic-lookup.component';
Lookup = 18, // LOOKUP
LookupName = 19, // LOOKUP_NAME
}
@Component({ @Component({
selector: 'ds-dynamic-form-control', selector: 'ds-dynamic-form-control',
@@ -88,67 +85,68 @@ export class DsDynamicFormControlComponent extends DynamicFormControlContainerCo
@Output('dfChange') change: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>(); @Output('dfChange') change: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
@Output('dfFocus') focus: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>(); @Output('dfFocus') focus: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
/* tslint:enable:no-output-rename */ /* tslint:enable:no-output-rename */
@ViewChild('componentViewContainer', {read: ViewContainerRef}) componentViewContainerRef: ViewContainerRef;
type: NGBootstrapFormControlType | null; get componentType(): Type<DynamicFormControl> | null {
return this.layoutService.getCustomComponentType(this.model) || DsDynamicFormControlComponent.getFormControlType(this.model);
}
readonly componentType: Type<DynamicFormControl> | null; static getFormControlType(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
static getFormControlType(model: DynamicFormControlModel): NGBootstrapFormControlType | null {
switch (model.type) { switch (model.type) {
case DYNAMIC_FORM_CONTROL_TYPE_ARRAY: case DYNAMIC_FORM_CONTROL_TYPE_ARRAY:
return NGBootstrapFormControlType.Array; return DynamicNGBootstrapFormArrayComponent;
case DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX: case DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX:
return NGBootstrapFormControlType.Checkbox; return DynamicNGBootstrapCheckboxComponent;
case DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX_GROUP: case DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX_GROUP:
return (model instanceof DynamicListCheckboxGroupModel) ? NGBootstrapFormControlType.List : NGBootstrapFormControlType.CheckboxGroup; return (model instanceof DynamicListCheckboxGroupModel) ? DsDynamicListComponent : DynamicNGBootstrapCheckboxGroupComponent;
case DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER: case DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER:
const datepickerModel = model as DynamicDatePickerModel; const datepickerModel = model as DynamicDatePickerModel;
return datepickerModel.inline ? NGBootstrapFormControlType.Calendar : NGBootstrapFormControlType.DatePicker; return datepickerModel.inline ? DynamicNGBootstrapCalendarComponent : DynamicNGBootstrapDatePickerComponent;
case DYNAMIC_FORM_CONTROL_TYPE_GROUP: case DYNAMIC_FORM_CONTROL_TYPE_GROUP:
return NGBootstrapFormControlType.Group; return DynamicNGBootstrapFormGroupComponent;
case DYNAMIC_FORM_CONTROL_TYPE_INPUT: case DYNAMIC_FORM_CONTROL_TYPE_INPUT:
return NGBootstrapFormControlType.Input; return DynamicNGBootstrapInputComponent;
case DYNAMIC_FORM_CONTROL_TYPE_RADIO_GROUP: case DYNAMIC_FORM_CONTROL_TYPE_RADIO_GROUP:
return (model instanceof DynamicListRadioGroupModel) ? NGBootstrapFormControlType.List : NGBootstrapFormControlType.RadioGroup; return (model instanceof DynamicListRadioGroupModel) ? DsDynamicListComponent : DynamicNGBootstrapRadioGroupComponent;
case DYNAMIC_FORM_CONTROL_TYPE_SELECT: case DYNAMIC_FORM_CONTROL_TYPE_SELECT:
return NGBootstrapFormControlType.Select; return DynamicNGBootstrapSelectComponent;
case DYNAMIC_FORM_CONTROL_TYPE_TEXTAREA: case DYNAMIC_FORM_CONTROL_TYPE_TEXTAREA:
return NGBootstrapFormControlType.TextArea; return DynamicNGBootstrapTextAreaComponent;
case DYNAMIC_FORM_CONTROL_TYPE_TIMEPICKER: case DYNAMIC_FORM_CONTROL_TYPE_TIMEPICKER:
return NGBootstrapFormControlType.TimePicker; return DynamicNGBootstrapTimePickerComponent;
case DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD: case DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD:
return NGBootstrapFormControlType.TypeAhead; return DsDynamicTypeaheadComponent;
case DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN: case DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN:
return NGBootstrapFormControlType.ScrollableDropdown; return DsDynamicScrollableDropdownComponent;
case DYNAMIC_FORM_CONTROL_TYPE_TAG: case DYNAMIC_FORM_CONTROL_TYPE_TAG:
return NGBootstrapFormControlType.Tag; return DsDynamicTagComponent;
case DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP: case DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP:
return NGBootstrapFormControlType.Relation; return DsDynamicGroupComponent;
case DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER: case DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER:
return NGBootstrapFormControlType.Date; return DsDatePickerComponent;
case DYNAMIC_FORM_CONTROL_TYPE_LOOKUP: case DYNAMIC_FORM_CONTROL_TYPE_LOOKUP:
return NGBootstrapFormControlType.Lookup; return DsDynamicLookupComponent;
case DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME: case DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME:
return NGBootstrapFormControlType.LookupName; return DsDynamicLookupComponent;
default: default:
return null; return null;
@@ -163,11 +161,7 @@ export class DsDynamicFormControlComponent extends DynamicFormControlContainerCo
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes) { if (changes) {
// super.ngOnChanges(changes); super.ngOnChanges(changes);
}
if (changes.model) {
this.type = DsDynamicFormControlComponent.getFormControlType(this.model);
} }
} }

View File

@@ -4,7 +4,7 @@ import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { DynamicFormValidationService } from '@ng-dynamic-forms/core'; import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
import { DsDatePickerComponent } from './date-picker.component'; import { DsDatePickerComponent } from './date-picker.component';
import { DynamicDsDatePickerModel } from './date-picker.model'; import { DynamicDsDatePickerModel } from './date-picker.model';
@@ -52,10 +52,8 @@ describe('DsDatePickerComponent test suite', () => {
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
DsDatePickerComponent, DsDatePickerComponent,
DynamicFormValidationService, {provide: DynamicFormLayoutService, useValue: {}},
FormBuilderService, {provide: DynamicFormValidationService, useValue: {}}
FormComponent,
FormService
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -70,7 +68,6 @@ describe('DsDatePickerComponent test suite', () => {
[bindId]='bindId' [bindId]='bindId'
[group]='group' [group]='group'
[model]='model' [model]='model'
[showErrorMessages]='showErrorMessages'
(blur)='onBlur($event)' (blur)='onBlur($event)'
(change)='onValueChange($event)' (change)='onValueChange($event)'
(focus)='onFocus($event)'></ds-date-picker>`; (focus)='onFocus($event)'></ds-date-picker>`;

View File

@@ -1,7 +1,12 @@
import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { DynamicDsDatePickerModel } from './date-picker.model'; import { DynamicDsDatePickerModel } from './date-picker.model';
import { hasNoValue, hasValue, isNotEmpty } from '../../../../../empty.util'; import { hasValue } from '../../../../../empty.util';
import {
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
export const DS_DATE_PICKER_SEPARATOR = '-'; export const DS_DATE_PICKER_SEPARATOR = '-';
@@ -11,11 +16,11 @@ export const DS_DATE_PICKER_SEPARATOR = '-';
templateUrl: './date-picker.component.html', templateUrl: './date-picker.component.html',
}) })
export class DsDatePickerComponent implements OnInit { export class DsDatePickerComponent extends DynamicFormControlComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicDsDatePickerModel; @Input() model: DynamicDsDatePickerModel;
@Input() showErrorMessages = false; // @Input() showErrorMessages = false;
// @Input() // @Input()
// minDate; // minDate;
// @Input() // @Input()
@@ -49,6 +54,12 @@ export class DsDatePickerComponent implements OnInit {
disabledMonth = true; disabledMonth = true;
disabledDay = true; disabledDay = true;
constructor(protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
}
ngOnInit() { ngOnInit() {
const now = new Date(); const now = new Date();
this.initialYear = now.getFullYear(); this.initialYear = now.getFullYear();

View File

@@ -3,8 +3,6 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/c
import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing'; import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DynamicFormValidationService } from '@ng-dynamic-forms/core';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@@ -20,12 +18,15 @@ import { FormBuilderService } from '../../../form-builder.service';
import { FormService } from '../../../../form.service'; import { FormService } from '../../../../form.service';
import { GLOBAL_CONFIG } from '../../../../../../../config'; import { GLOBAL_CONFIG } from '../../../../../../../config';
import { FormComponent } from '../../../../form.component'; import { FormComponent } from '../../../../form.component';
import { AppState } from '../../../../../../app.reducer';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Chips } from '../../../../../chips/models/chips.model'; import { Chips } from '../../../../../chips/models/chips.model';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { DsDynamicInputModel } from '../ds-dynamic-input.model'; import { DsDynamicInputModel } from '../ds-dynamic-input.model';
import { createTestComponent } from '../../../../../testing/utils'; import { createTestComponent } from '../../../../../testing/utils';
import { getMockFormBuilderService } from '../../../../../mocks/mock-form-builder-service';
import { getMockFormService } from '../../../../../mocks/mock-form-service';
import { MockComponent } from 'ng-mocks';
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
export const FORM_GROUP_TEST_MODEL_CONFIG = { export const FORM_GROUP_TEST_MODEL_CONFIG = {
disabled: false, disabled: false,
@@ -90,18 +91,13 @@ describe('DsDynamicGroupComponent test suite', () => {
let groupComp: DsDynamicGroupComponent; let groupComp: DsDynamicGroupComponent;
let testFixture: ComponentFixture<TestComponent>; let testFixture: ComponentFixture<TestComponent>;
let groupFixture: ComponentFixture<DsDynamicGroupComponent>; let groupFixture: ComponentFixture<DsDynamicGroupComponent>;
let modelValue: any; // let modelValue: any;
let html; let html;
let control1: FormControl; let control1: FormControl;
let model1: DsDynamicInputModel; let model1: DsDynamicInputModel;
let control2: FormControl; let control2: FormControl;
let model2: DsDynamicInputModel; let model2: DsDynamicInputModel;
const store: Store<AppState> = jasmine.createSpyObj('store', {
dispatch: {},
select: observableOf(true)
});
// async beforeEach // async beforeEach
beforeEach(async(() => { beforeEach(async(() => {
@@ -114,19 +110,18 @@ describe('DsDynamicGroupComponent test suite', () => {
TranslateModule.forRoot() TranslateModule.forRoot()
], ],
declarations: [ declarations: [
FormComponent, MockComponent(FormComponent),
DsDynamicGroupComponent, DsDynamicGroupComponent,
TestComponent, TestComponent,
], // declare the test component ], // declare the test component
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicGroupComponent, DsDynamicGroupComponent,
DynamicFormValidationService, {provide: FormBuilderService, useValue: getMockFormBuilderService()},
FormBuilderService, {provide: FormService, useValue: getMockFormService()},
FormComponent,
FormService,
{provide: GLOBAL_CONFIG, useValue: config}, {provide: GLOBAL_CONFIG, useValue: config},
{provide: Store, useValue: store}, {provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -140,7 +135,6 @@ describe('DsDynamicGroupComponent test suite', () => {
<ds-dynamic-group [model]="model" <ds-dynamic-group [model]="model"
[formId]="formId" [formId]="formId"
[group]="group" [group]="group"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onValueChange($event)" (change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-group>`; (focus)="onFocus($event)"></ds-dynamic-group>`;
@@ -163,7 +157,6 @@ describe('DsDynamicGroupComponent test suite', () => {
groupComp.formId = 'testForm'; groupComp.formId = 'testForm';
groupComp.group = FORM_GROUP_TEST_GROUP; groupComp.group = FORM_GROUP_TEST_GROUP;
groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG); groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
groupComp.showErrorMessages = false;
groupFixture.detectChanges(); groupFixture.detectChanges();
control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl; control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl;
@@ -179,123 +172,123 @@ describe('DsDynamicGroupComponent test suite', () => {
groupComp = null; groupComp = null;
}); });
it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => { // it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel; // const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel;
const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly); // const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
const chips = new Chips([], 'value', 'dc.contributor.author'); // const chips = new Chips([], 'value', 'dc.contributor.author');
//
expect(groupComp.formCollapsed).toEqual(observableOf(false)); // expect(groupComp.formCollapsed).toEqual(observableOf(false));
expect(groupComp.formModel.length).toEqual(formModel.length); // expect(groupComp.formModel.length).toEqual(formModel.length);
expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems()); // expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
})); // }));
//
it('should save a new chips item', () => { // it('should save a new chips item', () => {
control1.setValue('test author'); // control1.setValue('test author');
(model1 as any).value = new FormFieldMetadataValueObject('test author'); // (model1 as any).value = new FormFieldMetadataValueObject('test author');
control2.setValue('test affiliation'); // control2.setValue('test affiliation');
(model2 as any).value = new FormFieldMetadataValueObject('test affiliation'); // (model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
modelValue = [{ // modelValue = [{
'dc.contributor.author': new FormFieldMetadataValueObject('test author'), // 'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation') // 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
}]; // }];
groupFixture.detectChanges(); // groupFixture.detectChanges();
//
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button'); // const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[0]; // const btnEl = buttons[0];
btnEl.click(); // btnEl.click();
//
expect(groupComp.chips.getChipsItems()).toEqual(modelValue); // expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
expect(groupComp.formCollapsed).toEqual(observableOf(true)); // expect(groupComp.formCollapsed).toEqual(observableOf(true));
}); // });
//
it('should clear form inputs', () => { // it('should clear form inputs', () => {
control1.setValue('test author'); // control1.setValue('test author');
(model1 as any).value = new FormFieldMetadataValueObject('test author'); // (model1 as any).value = new FormFieldMetadataValueObject('test author');
control2.setValue('test affiliation'); // control2.setValue('test affiliation');
(model2 as any).value = new FormFieldMetadataValueObject('test affiliation'); // (model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
//
groupFixture.detectChanges(); // groupFixture.detectChanges();
//
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button'); // const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[2]; // const btnEl = buttons[2];
btnEl.click(); // btnEl.click();
//
expect(control1.value).toBeNull(); // expect(control1.value).toBeNull();
expect(control2.value).toBeNull(); // expect(control2.value).toBeNull();
expect(groupComp.formCollapsed).toEqual(observableOf(false)); // expect(groupComp.formCollapsed).toEqual(observableOf(false));
}); // });
}); // });
//
describe('when init model value is not empty', () => { // describe('when init model value is not empty', () => {
beforeEach(() => { // beforeEach(() => {
//
groupFixture = TestBed.createComponent(DsDynamicGroupComponent); // groupFixture = TestBed.createComponent(DsDynamicGroupComponent);
groupComp = groupFixture.componentInstance; // FormComponent test instance // groupComp = groupFixture.componentInstance; // FormComponent test instance
groupComp.formId = 'testForm'; // groupComp.formId = 'testForm';
groupComp.group = FORM_GROUP_TEST_GROUP; // groupComp.group = FORM_GROUP_TEST_GROUP;
groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG); // groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
modelValue = [{ // modelValue = [{
'dc.contributor.author': new FormFieldMetadataValueObject('test author'), // 'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation') // 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
}]; // }];
groupComp.model.value = modelValue; // groupComp.model.value = modelValue;
groupComp.showErrorMessages = false; // groupComp.showErrorMessages = false;
groupFixture.detectChanges(); // groupFixture.detectChanges();
//
}); // });
//
afterEach(() => { // afterEach(() => {
groupFixture.destroy(); // groupFixture.destroy();
groupComp = null; // groupComp = null;
}); // });
//
it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => { // it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel; // const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel;
const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly); // const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
const chips = new Chips(modelValue, 'value', 'dc.contributor.author'); // const chips = new Chips(modelValue, 'value', 'dc.contributor.author');
//
expect(groupComp.formCollapsed).toEqual(observableOf(true)); // expect(groupComp.formCollapsed).toEqual(observableOf(true));
expect(groupComp.formModel.length).toEqual(formModel.length); // expect(groupComp.formModel.length).toEqual(formModel.length);
expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems()); // expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
})); // }));
//
it('should modify existing chips item', inject([FormBuilderService], (service: FormBuilderService) => { // it('should modify existing chips item', inject([FormBuilderService], (service: FormBuilderService) => {
groupComp.onChipSelected(0); // groupComp.onChipSelected(0);
groupFixture.detectChanges(); // groupFixture.detectChanges();
//
control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl; // control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl;
model1 = service.findById('dc_contributor_author', groupComp.formModel) as DsDynamicInputModel; // model1 = service.findById('dc_contributor_author', groupComp.formModel) as DsDynamicInputModel;
//
control1.setValue('test author modify'); // control1.setValue('test author modify');
(model1 as any).value = new FormFieldMetadataValueObject('test author modify'); // (model1 as any).value = new FormFieldMetadataValueObject('test author modify');
//
modelValue = [{ // modelValue = [{
'dc.contributor.author': new FormFieldMetadataValueObject('test author modify'), // 'dc.contributor.author': new FormFieldMetadataValueObject('test author modify'),
'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation') // 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
}]; // }];
groupFixture.detectChanges(); // groupFixture.detectChanges();
//
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button'); // const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[0]; // const btnEl = buttons[0];
btnEl.click(); // btnEl.click();
//
groupFixture.detectChanges(); // groupFixture.detectChanges();
//
expect(groupComp.chips.getChipsItems()).toEqual(modelValue); // expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
expect(groupComp.formCollapsed).toEqual(observableOf(true)); // expect(groupComp.formCollapsed).toEqual(observableOf(true));
})); // }));
//
it('should delete existing chips item', () => { // it('should delete existing chips item', () => {
groupComp.onChipSelected(0); // groupComp.onChipSelected(0);
groupFixture.detectChanges(); // groupFixture.detectChanges();
//
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button'); // const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[1]; // const btnEl = buttons[1];
btnEl.click(); // btnEl.click();
//
expect(groupComp.chips.getChipsItems()).toEqual([]); // expect(groupComp.chips.getChipsItems()).toEqual([]);
expect(groupComp.formCollapsed).toEqual(observableOf(false)); // expect(groupComp.formCollapsed).toEqual(observableOf(false));
}); // });
}); });
}); });

View File

@@ -1,4 +1,4 @@
import {of as observableOf, Observable , Subscription } from 'rxjs'; import { of as observableOf, Subscription } from 'rxjs';
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
@@ -10,7 +10,14 @@ import {
Output, Output,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicInputModel } from '@ng-dynamic-forms/core'; import {
DynamicFormControlComponent,
DynamicFormControlModel,
DynamicFormGroupModel,
DynamicFormLayoutService,
DynamicFormValidationService,
DynamicInputModel
} from '@ng-dynamic-forms/core';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { DynamicGroupModel, PLACEHOLDER_PARENT_METADATA } from './dynamic-group.model'; import { DynamicGroupModel, PLACEHOLDER_PARENT_METADATA } from './dynamic-group.model';
@@ -26,8 +33,6 @@ import { GlobalConfig } from '../../../../../../../config/global-config.interfac
import { GLOBAL_CONFIG } from '../../../../../../../config'; import { GLOBAL_CONFIG } from '../../../../../../../config';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { hasOnlyEmptyProperties } from '../../../../../object.util'; import { hasOnlyEmptyProperties } from '../../../../../object.util';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model';
@Component({ @Component({
selector: 'ds-dynamic-group', selector: 'ds-dynamic-group',
@@ -35,12 +40,12 @@ import { AuthorityValueModel } from '../../../../../../core/integration/models/a
templateUrl: './dynamic-group.component.html', templateUrl: './dynamic-group.component.html',
animations: [shrinkInOut] animations: [shrinkInOut]
}) })
export class DsDynamicGroupComponent implements OnDestroy, OnInit { export class DsDynamicGroupComponent extends DynamicFormControlComponent implements OnDestroy, OnInit {
@Input() formId: string; @Input() formId: string;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicGroupModel; @Input() model: DynamicGroupModel;
@Input() showErrorMessages = false; // @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -59,11 +64,16 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
private formBuilderService: FormBuilderService, private formBuilderService: FormBuilderService,
private formService: FormService, private formService: FormService,
private cdr: ChangeDetectorRef) { private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
} }
ngOnInit() { ngOnInit() {
const config = {rows: this.model.formConfiguration} as SubmissionFormsModel; const config = { rows: this.model.formConfiguration } as SubmissionFormsModel;
if (!this.model.isEmpty()) { if (!this.model.isEmpty()) {
this.formCollapsed = observableOf(true); this.formCollapsed = observableOf(true);
} }

View File

@@ -9,7 +9,12 @@ import { DsDynamicListComponent } from './dynamic-list.component';
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model'; import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model'; import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
import { FormBuilderService } from '../../../form-builder.service'; import { FormBuilderService } from '../../../form-builder.service';
import { DynamicFormControlLayout, DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import {
DynamicFormControlLayout,
DynamicFormLayoutService,
DynamicFormsCoreModule,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
import { AuthorityService } from '../../../../../../core/integration/authority.service'; import { AuthorityService } from '../../../../../../core/integration/authority.service';
import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub'; import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub';
@@ -90,12 +95,13 @@ describe('DsDynamicListComponent test suite', () => {
TestComponent, TestComponent,
], // declare the test component ], // declare the test component
providers: [ providers: [
AuthorityService,
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicListComponent, DsDynamicListComponent,
DynamicFormValidationService, DynamicFormValidationService,
FormBuilderService, FormBuilderService,
{provide: AuthorityService, useValue: authorityServiceStub}, {provide: AuthorityService, useValue: authorityServiceStub},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -110,7 +116,6 @@ describe('DsDynamicListComponent test suite', () => {
[bindId]="bindId" [bindId]="bindId"
[group]="group" [group]="group"
[model]="model" [model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onValueChange($event)" (change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-list>`; (focus)="onFocus($event)"></ds-dynamic-list>`;

View File

@@ -7,7 +7,11 @@ import { IntegrationSearchOptions } from '../../../../../../core/integration/mod
import { hasValue, isNotEmpty } from '../../../../../empty.util'; import { hasValue, isNotEmpty } from '../../../../../empty.util';
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model'; import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
import { FormBuilderService } from '../../../form-builder.service'; import { FormBuilderService } from '../../../form-builder.service';
import { DynamicCheckboxModel } from '@ng-dynamic-forms/core'; import {
DynamicCheckboxModel,
DynamicFormControlComponent, DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model'; import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model';
import { DynamicListRadioGroupModel } from './dynamic-list-radio-group.model'; import { DynamicListRadioGroupModel } from './dynamic-list-radio-group.model';
import { IntegrationData } from '../../../../../../core/integration/integration-data'; import { IntegrationData } from '../../../../../../core/integration/integration-data';
@@ -25,11 +29,11 @@ export interface ListItem {
templateUrl: './dynamic-list.component.html' templateUrl: './dynamic-list.component.html'
}) })
export class DsDynamicListComponent implements OnInit { export class DsDynamicListComponent extends DynamicFormControlComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicListCheckboxGroupModel | DynamicListRadioGroupModel; @Input() model: DynamicListCheckboxGroupModel | DynamicListRadioGroupModel;
@Input() showErrorMessages = false; // @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -41,7 +45,11 @@ export class DsDynamicListComponent implements OnInit {
constructor(private authorityService: AuthorityService, constructor(private authorityService: AuthorityService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
private formBuilderService: FormBuilderService) { private formBuilderService: FormBuilderService,
protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
} }
ngOnInit() { ngOnInit() {
@@ -110,7 +118,10 @@ export class DsDynamicListComponent implements OnInit {
if (this.model.repeatable) { if (this.model.repeatable) {
this.formBuilderService.addFormGroupControl(listGroup, (this.model as DynamicListCheckboxGroupModel), new DynamicCheckboxModel(item)); this.formBuilderService.addFormGroupControl(listGroup, (this.model as DynamicListCheckboxGroupModel), new DynamicCheckboxModel(item));
} else { } else {
(this.model as DynamicListRadioGroupModel).options.push({label: item.label, value: option}); (this.model as DynamicListRadioGroupModel).options.push({
label: item.label,
value: option
});
} }
tempList.push(item); tempList.push(item);
itemsPerGroup++; itemsPerGroup++;

View File

@@ -6,7 +6,11 @@ import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@ang
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model'; import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
import { DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import {
DynamicFormLayoutService,
DynamicFormsCoreModule,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
import { AuthorityService } from '../../../../../../core/integration/authority.service'; import { AuthorityService } from '../../../../../../core/integration/authority.service';
import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub'; import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub';
@@ -102,11 +106,9 @@ describe('Dynamic Lookup component', () => {
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicLookupComponent, DsDynamicLookupComponent,
DynamicFormValidationService,
FormBuilderService,
FormComponent,
FormService,
{provide: AuthorityService, useValue: authorityServiceStub}, {provide: AuthorityService, useValue: authorityServiceStub},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -121,7 +123,6 @@ describe('Dynamic Lookup component', () => {
[bindId]="bindId" [bindId]="bindId"
[group]="group" [group]="group"
[model]="model" [model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onValueChange($event)" (change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-lookup>`; (focus)="onFocus($event)"></ds-dynamic-lookup>`;

View File

@@ -13,17 +13,22 @@ import { Subscription } from 'rxjs';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model'; import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model';
import { DynamicLookupNameModel } from './dynamic-lookup-name.model'; import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
import {
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
@Component({ @Component({
selector: 'ds-dynamic-lookup', selector: 'ds-dynamic-lookup',
styleUrls: ['./dynamic-lookup.component.scss'], styleUrls: ['./dynamic-lookup.component.scss'],
templateUrl: './dynamic-lookup.component.html' templateUrl: './dynamic-lookup.component.html'
}) })
export class DsDynamicLookupComponent implements OnDestroy, OnInit { export class DsDynamicLookupComponent extends DynamicFormControlComponent implements OnDestroy, OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicLookupModel | DynamicLookupNameModel; @Input() model: DynamicLookupModel | DynamicLookupNameModel;
@Input() showErrorMessages = false; // @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -39,7 +44,11 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit {
protected sub: Subscription; protected sub: Subscription;
constructor(private authorityService: AuthorityService, constructor(private authorityService: AuthorityService,
private cdr: ChangeDetectorRef) { private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
} }
ngOnInit() { ngOnInit() {

View File

@@ -6,7 +6,11 @@ import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@ang
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model'; import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
import { DynamicFormsCoreModule } from '@ng-dynamic-forms/core'; import {
DynamicFormLayoutService,
DynamicFormsCoreModule,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
import { AuthorityService } from '../../../../../../core/integration/authority.service'; import { AuthorityService } from '../../../../../../core/integration/authority.service';
import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub'; import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub';
@@ -77,6 +81,8 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicScrollableDropdownComponent, DsDynamicScrollableDropdownComponent,
{provide: AuthorityService, useValue: authorityServiceStub}, {provide: AuthorityService, useValue: authorityServiceStub},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -90,7 +96,6 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
<ds-dynamic-scrollable-dropdown [bindId]="bindId" <ds-dynamic-scrollable-dropdown [bindId]="bindId"
[group]="group" [group]="group"
[model]="model" [model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onValueChange($event)" (change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-scrollable-dropdown>`; (focus)="onFocus($event)"></ds-dynamic-scrollable-dropdown>`;

View File

@@ -11,17 +11,22 @@ import { IntegrationSearchOptions } from '../../../../../../core/integration/mod
import { IntegrationData } from '../../../../../../core/integration/integration-data'; import { IntegrationData } from '../../../../../../core/integration/integration-data';
import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model'; import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import {
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
@Component({ @Component({
selector: 'ds-dynamic-scrollable-dropdown', selector: 'ds-dynamic-scrollable-dropdown',
styleUrls: ['./dynamic-scrollable-dropdown.component.scss'], styleUrls: ['./dynamic-scrollable-dropdown.component.scss'],
templateUrl: './dynamic-scrollable-dropdown.component.html' templateUrl: './dynamic-scrollable-dropdown.component.html'
}) })
export class DsDynamicScrollableDropdownComponent implements OnInit { export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicScrollableDropdownModel; @Input() model: DynamicScrollableDropdownModel;
@Input() showErrorMessages = false; // @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -33,7 +38,13 @@ export class DsDynamicScrollableDropdownComponent implements OnInit {
protected searchOptions: IntegrationSearchOptions; protected searchOptions: IntegrationSearchOptions;
constructor(private authorityService: AuthorityService, private cdr: ChangeDetectorRef) {} constructor(private authorityService: AuthorityService,
private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
}
ngOnInit() { ngOnInit() {
this.searchOptions = new IntegrationSearchOptions( this.searchOptions = new IntegrationSearchOptions(

View File

@@ -4,7 +4,11 @@ import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angul
import { async, ComponentFixture, fakeAsync, flush, inject, TestBed, } from '@angular/core/testing'; import { async, ComponentFixture, fakeAsync, flush, inject, TestBed, } from '@angular/core/testing';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { DynamicFormsCoreModule } from '@ng-dynamic-forms/core'; import {
DynamicFormLayoutService,
DynamicFormsCoreModule,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
import { NgbModule, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
@@ -86,6 +90,8 @@ describe('DsDynamicTagComponent test suite', () => {
DsDynamicTagComponent, DsDynamicTagComponent,
{provide: AuthorityService, useValue: authorityServiceStub}, {provide: AuthorityService, useValue: authorityServiceStub},
{provide: GLOBAL_CONFIG, useValue: {} as GlobalConfig}, {provide: GLOBAL_CONFIG, useValue: {} as GlobalConfig},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -99,7 +105,6 @@ describe('DsDynamicTagComponent test suite', () => {
<ds-dynamic-tag [bindId]="bindId" <ds-dynamic-tag [bindId]="bindId"
[group]="group" [group]="group"
[model]="model" [model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onValueChange($event)" (change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-tag>`; (focus)="onFocus($event)"></ds-dynamic-tag>`;

View File

@@ -14,17 +14,22 @@ import { hasValue, isNotEmpty } from '../../../../../empty.util';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { GlobalConfig } from '../../../../../../../config/global-config.interface'; import { GlobalConfig } from '../../../../../../../config/global-config.interface';
import { GLOBAL_CONFIG } from '../../../../../../../config'; import { GLOBAL_CONFIG } from '../../../../../../../config';
import {
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
@Component({ @Component({
selector: 'ds-dynamic-tag', selector: 'ds-dynamic-tag',
styleUrls: ['./dynamic-tag.component.scss'], styleUrls: ['./dynamic-tag.component.scss'],
templateUrl: './dynamic-tag.component.html' templateUrl: './dynamic-tag.component.html'
}) })
export class DsDynamicTagComponent implements OnInit { export class DsDynamicTagComponent extends DynamicFormControlComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicTagModel; @Input() model: DynamicTagModel;
@Input() showErrorMessages = false; // @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -72,7 +77,11 @@ export class DsDynamicTagComponent implements OnInit {
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
private authorityService: AuthorityService, private authorityService: AuthorityService,
private cdr: ChangeDetectorRef) { private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
} }
ngOnInit() { ngOnInit() {

View File

@@ -4,10 +4,13 @@ import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angul
import { async, ComponentFixture, fakeAsync, inject, TestBed, } from '@angular/core/testing'; import { async, ComponentFixture, fakeAsync, inject, TestBed, } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import 'rxjs/add/observable/of';
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model'; import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
import { DynamicFormsCoreModule } from '@ng-dynamic-forms/core'; import {
DynamicFormLayoutService,
DynamicFormsCoreModule,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
import { AuthorityService } from '../../../../../../core/integration/authority.service'; import { AuthorityService } from '../../../../../../core/integration/authority.service';
import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub'; import { AuthorityServiceStub } from '../../../../../testing/authority-service-stub';
@@ -70,7 +73,8 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicTypeaheadComponent, DsDynamicTypeaheadComponent,
{provide: AuthorityService, useValue: authorityServiceStub}, {provide: AuthorityService, useValue: authorityServiceStub},
{provide: GLOBAL_CONFIG, useValue: {} as GlobalConfig}, {provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -84,7 +88,6 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
<ds-dynamic-typeahead [bindId]="bindId" <ds-dynamic-typeahead [bindId]="bindId"
[group]="group" [group]="group"
[model]="model" [model]="model"
[showErrorMessages]="showErrorMessages"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onValueChange($event)" (change)="onValueChange($event)"
(focus)="onFocus($event)"></ds-dynamic-typeahead>`; (focus)="onFocus($event)"></ds-dynamic-typeahead>`;

View File

@@ -11,13 +11,18 @@ import { DynamicTypeaheadModel } from './dynamic-typeahead.model';
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model'; import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
import { isEmpty, isNotEmpty } from '../../../../../empty.util'; import { isEmpty, isNotEmpty } from '../../../../../empty.util';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import {
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
@Component({ @Component({
selector: 'ds-dynamic-typeahead', selector: 'ds-dynamic-typeahead',
styleUrls: ['./dynamic-typeahead.component.scss'], styleUrls: ['./dynamic-typeahead.component.scss'],
templateUrl: './dynamic-typeahead.component.html' templateUrl: './dynamic-typeahead.component.html'
}) })
export class DsDynamicTypeaheadComponent implements OnInit { export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicTypeaheadModel; @Input() model: DynamicTypeaheadModel;
@@ -67,7 +72,12 @@ export class DsDynamicTypeaheadComponent implements OnInit {
tap(() => this.changeSearchingStatus(false)), tap(() => this.changeSearchingStatus(false)),
merge(this.hideSearchingWhenUnsubscribed),); merge(this.hideSearchingWhenUnsubscribed),);
constructor(private authorityService: AuthorityService, private cdr: ChangeDetectorRef) { constructor(private authorityService: AuthorityService,
private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
} }
ngOnInit() { ngOnInit() {

View File

@@ -49,6 +49,7 @@ import { FormFieldMetadataValueObject } from './models/form-field-metadata-value
import { DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model'; import { DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model';
import { DynamicLookupNameModel } from './ds-dynamic-form-ui/models/lookup/dynamic-lookup-name.model'; import { DynamicLookupNameModel } from './ds-dynamic-form-ui/models/lookup/dynamic-lookup-name.model';
import { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model'; import { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
import { getMockFormBuilderService } from '../../mocks/mock-form-builder-service';
describe('FormBuilderService test suite', () => { describe('FormBuilderService test suite', () => {
@@ -69,9 +70,7 @@ describe('FormBuilderService test suite', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ReactiveFormsModule], imports: [ReactiveFormsModule],
providers: [ providers: [
FormBuilderService, {provide: FormBuilderService, useValue: getMockFormBuilderService()},
DynamicFormService,
DynamicFormValidationService,
{provide: NG_VALIDATORS, useValue: testValidator, multi: true}, {provide: NG_VALIDATORS, useValue: testValidator, multi: true},
{provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true} {provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true}
] ]

View File

@@ -3,15 +3,14 @@ import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import 'rxjs/add/observable/of';
import { import {
DynamicFormArrayModel, DynamicFormArrayModel,
DynamicFormControlEvent, DynamicFormControlEvent,
DynamicFormControlModel, DynamicFormControlModel,
DynamicFormValidationService,
DynamicInputModel DynamicInputModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { Store } from '@ngrx/store'; import { ActionsSubject, Store } from '@ngrx/store';
import { of as observableOf } from 'rxjs';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@@ -19,11 +18,12 @@ import { FormComponent } from './form.component';
import { FormService } from './form.service'; import { FormService } from './form.service';
import { FormBuilderService } from './builder/form-builder.service'; import { FormBuilderService } from './builder/form-builder.service';
import { FormState } from './form.reducer'; import { FormState } from './form.reducer';
import { FormChangeAction, FormStatusChangeAction } from './form.actions'; import { FormAddError, FormChangeAction, FormStatusChangeAction } from './form.actions';
import { MockStore } from '../testing/mock-store';
import { FormFieldMetadataValueObject } from './builder/models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from './builder/models/form-field-metadata-value.model';
import { GLOBAL_CONFIG } from '../../../config'; import { GLOBAL_CONFIG } from '../../../config';
import { createTestComponent } from '../testing/utils'; import { createTestComponent } from '../testing/utils';
import { getMockFormService } from '../mocks/mock-form-service';
import { getMockFormBuilderService } from '../mocks/mock-form-builder-service';
export const TEST_FORM_MODEL = [ export const TEST_FORM_MODEL = [
@@ -93,7 +93,7 @@ export const TEST_FORM_MODEL_WITH_ARRAY = [
}) })
]; ];
describe('FormComponent test suite', () => { fdescribe('FormComponent test suite', () => {
let testComp: TestComponent; let testComp: TestComponent;
let formComp: FormComponent; let formComp: FormComponent;
let testFixture: ComponentFixture<TestComponent>; let testFixture: ComponentFixture<TestComponent>;
@@ -122,7 +122,7 @@ describe('FormComponent test suite', () => {
}; };
let html; let html;
const store: MockStore<FormState> = new MockStore<FormState>(formState); const store = new Store<FormState>(observableOf({}), new ActionsSubject(), undefined);
// async beforeEach // async beforeEach
beforeEach(async(() => { beforeEach(async(() => {
@@ -142,10 +142,9 @@ describe('FormComponent test suite', () => {
], // declare the test component ], // declare the test component
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
DynamicFormValidationService, {provide: FormBuilderService, useValue: getMockFormBuilderService()},
FormBuilderService,
FormComponent, FormComponent,
FormService, {provide: FormService, useValue: getMockFormService()},
{provide: GLOBAL_CONFIG, useValue: config}, {provide: GLOBAL_CONFIG, useValue: config},
{ {
provide: Store, useValue: store provide: Store, useValue: store
@@ -200,18 +199,17 @@ describe('FormComponent test suite', () => {
}); });
it('should display form errors when errors are added to the state', () => { fit('should display form errors when errors are added to the state', () => {
const errors = [{ const error = {
fieldId: 'dc_title', fieldId: 'dc_title',
fieldIndex: 0, fieldIndex: 0,
message: 'error.validation.required' message: 'error.validation.required'
}]; };
formState.testForm.errors = errors; store.dispatch(new FormAddError(formComp.formId, error.fieldId, error.fieldIndex, error.message));
store.nextState(formState);
formFixture.detectChanges(); formFixture.detectChanges();
expect((formComp as any).formErrors).toEqual(errors); expect((formComp as any).formErrors[0]).toEqual(error);
}); });

View File

@@ -30,6 +30,7 @@ export interface FormState {
const initialState: FormState = Object.create(null); const initialState: FormState = Object.create(null);
export function formReducer(state = initialState, action: FormAction): FormState { export function formReducer(state = initialState, action: FormAction): FormState {
console.log('TEST');
switch (action.type) { switch (action.type) {
case FormActionTypes.FORM_INIT: { case FormActionTypes.FORM_INIT: {
@@ -67,6 +68,7 @@ export function formReducer(state = initialState, action: FormAction): FormState
} }
function addFormErrors(state: FormState, action: FormAddError) { function addFormErrors(state: FormState, action: FormAddError) {
console.log(state);
const formId = action.payload.formId; const formId = action.payload.formId;
if (hasValue(state[formId])) { if (hasValue(state[formId])) {
const error: FormError = { const error: FormError = {

View File

@@ -3,7 +3,8 @@ import { async, inject, TestBed } from '@angular/core/testing';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { import {
DynamicFormControlModel, DynamicFormGroupModel, DynamicFormControlModel,
DynamicFormGroupModel,
DynamicFormService, DynamicFormService,
DynamicFormValidationService, DynamicFormValidationService,
DynamicInputModel DynamicInputModel
@@ -13,7 +14,7 @@ import { FormService } from './form.service';
import { FormBuilderService } from './builder/form-builder.service'; import { FormBuilderService } from './builder/form-builder.service';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { formReducer } from './form.reducer'; import { formReducer } from './form.reducer';
import { GlobalConfig } from '../../../config/global-config.interface'; import { getMockFormBuilderService } from '../mocks/mock-form-builder-service';
describe('FormService test suite', () => { describe('FormService test suite', () => {
const config = { const config = {
@@ -93,9 +94,7 @@ describe('FormService test suite', () => {
StoreModule.forRoot({formReducer}) StoreModule.forRoot({formReducer})
], ],
providers: [ providers: [
DynamicFormService, {provide: FormBuilderService, useValue: getMockFormBuilderService()},
DynamicFormValidationService,
FormBuilderService,
] ]
}).compileComponents(); }).compileComponents();
})); }));

View File

@@ -0,0 +1,18 @@
import { FormBuilderService } from '../form/builder/form-builder.service';
import { FormControl, FormGroup } from '@angular/forms';
export function getMockFormBuilderService(): FormBuilderService {
return jasmine.createSpyObj('FormService', {
modelFromConfiguration: [],
createFormGroup: new FormGroup({}),
getValueFromModel: {},
getFormControlById: new FormControl(),
findById: {},
getPath: ['test', 'path'],
clearAllModelsValue : {},
insertFormArrayGroup: {},
isQualdrop: false
});
}

View File

@@ -0,0 +1,12 @@
import { FormService } from '../form/form.service';
export function getMockFormService(
id$: string = 'random_id'
): FormService {
return jasmine.createSpyObj('FormService', {
getUniqueId: id$,
resetForm: {},
validateAllFormFields: {}
});
}

View File

@@ -1,15 +1,4 @@
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { of as observableOf } from 'rxjs';
export function getMockStore<T>(): Store<T> {
return jasmine.createSpyObj('store', [
'select',
'dispatch',
'lift',
'next',
'error',
'complete',
'addReducer',
'removeReducer'
]);
}

View File

@@ -5,7 +5,6 @@ import { NotificationComponent } from './notification/notification.component';
import { Store, StoreModule } from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import { notificationsReducer } from './notifications.reducers'; import { notificationsReducer } from './notifications.reducers';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import 'rxjs/add/observable/of';
import { import {
NewNotificationAction, NewNotificationAction,
RemoveAllNotificationsAction, RemoveAllNotificationsAction,

View File

@@ -156,7 +156,6 @@ class TestComponent {
public max = 100; public max = 100;
public min = 0; public min = 0;
public initValue = 0; public initValue = 0;
public showErrorMessages = false;
public size = 4; public size = 4;
public value; public value;

View File

@@ -22,7 +22,7 @@ const ENV = process.env.ENV = process.env.NODE_ENV = 'test';
*/ */
module.exports = function (options) { module.exports = function (options) {
return { return {
mode: 'development',
/** /**
* Source map for Karma from the help of karma-sourcemap-loader & karma-webpack * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack
* *

1389
yarn.lock

File diff suppressed because it is too large Load Diff