diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index b898963af..d3812df07 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -20,7 +20,7 @@ import { AppComponentBase, AppLanguageDto, AppsStoreService, - allDataFromRoute, + allData, CanComponentDeactivate, ContentDto, ContentsService, @@ -74,7 +74,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone } public ngOnInit() { - const routeData = allDataFromRoute(this.route); + const routeData = allData(this.route); this.languages = routeData['appLanguages']; diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts index 9a04d6ba3..0c8b4ab76 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts @@ -17,7 +17,7 @@ import { } from './../messages'; import { - allDataFromRoute, + allData, AppComponentBase, AppLanguageDto, AppsStoreService, @@ -74,7 +74,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy } public ngOnInit() { - const routeData = allDataFromRoute(this.route); + const routeData = allData(this.route); this.languages = routeData['appLanguages']; diff --git a/src/Squidex/app/framework/angular/router-utils.spec.ts b/src/Squidex/app/framework/angular/router-utils.spec.ts new file mode 100644 index 000000000..e992b176d --- /dev/null +++ b/src/Squidex/app/framework/angular/router-utils.spec.ts @@ -0,0 +1,96 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { + allData, + allParams +} from './router-utils'; + +describe('RouterUtils', () => { + it('should concat all params from route', () => { + const snapshot = { + params: { + key1: 'key1' + }, + parent: { + params: { + key1: 'key1-parent', + key2: 'key2' + } + } + }; + + const params = allParams({ snapshot }); + + expect(params).toEqual({ + key1: 'key1', + key2: 'key2' + }); + }); + + it('should concat all params from snapshot', () => { + const snapshot = { + params: { + key1: 'key1' + }, + parent: { + params: { + key1: 'key1-parent', + key2: 'key2' + } + } + }; + + const params = allParams(snapshot); + + expect(params).toEqual({ + key1: 'key1', + key2: 'key2' + }); + }); + it('should concat all data from route', () => { + const snapshot = { + data: { + key1: 'key1' + }, + parent: { + data: { + key1: 'key1-parent', + key2: 'key2' + } + } + }; + + const params = allData({ snapshot }); + + expect(params).toEqual({ + key1: 'key1', + key2: 'key2' + }); + }); + + it('should concat all data from snapshot', () => { + const snapshot = { + data: { + key1: 'key1' + }, + parent: { + data: { + key1: 'key1-parent', + key2: 'key2' + } + } + }; + + const params = allData(snapshot); + + expect(params).toEqual({ + key1: 'key1', + key2: 'key2' + }); + }); +}); \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/router-utils.ts b/src/Squidex/app/framework/angular/router-utils.ts index ca49ef802..97034de9e 100644 --- a/src/Squidex/app/framework/angular/router-utils.ts +++ b/src/Squidex/app/framework/angular/router-utils.ts @@ -7,41 +7,36 @@ import { ActivatedRoute, ActivatedRouteSnapshot, Data, Params } from '@angular/router'; -export function allDataFromRoute(route: ActivatedRoute): Data { - return allData(route.snapshot); -} +export function allData(value: ActivatedRouteSnapshot | ActivatedRoute): Data { + let snapshot: ActivatedRouteSnapshot = value['snapshot'] || value; -export function allData(route: ActivatedRouteSnapshot): Data { let result: { [key: string]: any } = { }; - while (route) { - for (let key in route.data) { - if (route.data.hasOwnProperty(key) && !result[key]) { - result[key] = route.data[key]; + while (snapshot) { + for (let key in snapshot.data) { + if (snapshot.data.hasOwnProperty(key) && !result[key]) { + result[key] = snapshot.data[key]; } } - route = route.parent; + snapshot = snapshot.parent; } return result; } +export function allParams(value: ActivatedRouteSnapshot | ActivatedRoute): Params { + let snapshot: ActivatedRouteSnapshot = value['snapshot'] || value; -export function allParametersFromRoute(route: ActivatedRoute): Params { - return allParameters(route.snapshot); -} - -export function allParameters(route: ActivatedRouteSnapshot): Params { let result: { [key: string]: any } = { }; - while (route) { - for (let key of route.paramMap.keys) { - if (!result[key]) { - result[key] = route.paramMap.get(key); + while (snapshot) { + for (let key in snapshot.params) { + if (snapshot.params.hasOwnProperty(key) && !result[key]) { + result[key] = snapshot.params[key]; } } - route = route.parent; + snapshot = snapshot.parent; } return result; diff --git a/src/Squidex/app/framework/angular/validators.spec.ts b/src/Squidex/app/framework/angular/validators.spec.ts index 436f22ab1..b0cddcf1e 100644 --- a/src/Squidex/app/framework/angular/validators.spec.ts +++ b/src/Squidex/app/framework/angular/validators.spec.ts @@ -7,6 +7,7 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { DateTime } from './../utils/date-time'; import { ValidatorsEx } from './../'; describe('ValidatorsEx.between', () => { @@ -49,6 +50,32 @@ describe('ValidatorsEx.between', () => { }); }); +describe('ValidatorsEx.validDateTime', () => { + it('should return null validator if valid is not defined', () => { + const input = new FormControl(null); + + const error = ValidatorsEx.validDateTime()(input); + + expect(error).toBeNull(); + }); + + it('should return null if date time is valid', () => { + const input = new FormControl(DateTime.now().toISOString()); + + const error = ValidatorsEx.validDateTime()(input); + + expect(error).toBeNull(); + }); + + it('should return error if value is invalid date', () => { + const input = new FormControl('invalid'); + + const error = ValidatorsEx.validDateTime()(input); + + expect(error.validdatetime).toBeDefined(); + }); +}); + describe('ValidatorsEx.validValues', () => { it('should return null validator if values not defined', () => { const validator = ValidatorsEx.validValues(null!); diff --git a/src/Squidex/app/framework/angular/validators.ts b/src/Squidex/app/framework/angular/validators.ts index ba6abc758..7626cb193 100644 --- a/src/Squidex/app/framework/angular/validators.ts +++ b/src/Squidex/app/framework/angular/validators.ts @@ -85,7 +85,7 @@ export module ValidatorsEx { try { DateTime.parseISO_UTC(v); } catch (e) { - return { validdateTime: false }; + return { validdatetime: false }; } } diff --git a/src/Squidex/app/shared/components/history.component.ts b/src/Squidex/app/shared/components/history.component.ts index 9edef92e1..787952856 100644 --- a/src/Squidex/app/shared/components/history.component.ts +++ b/src/Squidex/app/shared/components/history.component.ts @@ -12,7 +12,7 @@ import { Observable } from 'rxjs'; import { AppComponentBase } from './app.component-base'; import { - allParametersFromRoute, + allParams, AppsStoreService, HistoryChannelUpdated, HistoryEventDto, @@ -34,7 +34,7 @@ export class HistoryComponent extends AppComponentBase { let channelPath = this.route.snapshot.data['channel']; if (channelPath) { - let params = allParametersFromRoute(this.route); + let params = allParams(this.route); for (let key in params) { if (params.hasOwnProperty(key)) { diff --git a/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts b/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts index 9f28bc24e..88e6950d7 100644 --- a/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts @@ -8,7 +8,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; -import { allParameters } from 'framework'; +import { allParams } from 'framework'; import { AppLanguageDto, AppLanguagesService } from './../services/app-languages.service'; @@ -21,7 +21,7 @@ export class ResolveAppLanguagesGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const params = allParameters(route); + const params = allParams(route); const appName = params['appName']; diff --git a/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts index 9f5194319..fb7979516 100644 --- a/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts +++ b/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts @@ -36,10 +36,22 @@ describe('ResolveContentGuard', () => { appsStore = Mock.ofType(ContentsService); }); - it('should throw if route does not contain parameter', () => { + it('should throw if route does not contain app name', () => { const guard = new ResolveContentGuard(appsStore.object, new RouterMockup()); - expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app and schema name and id.'); + expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app name.'); + }); + + it('should throw if route does not contain schema name', () => { + const guard = new ResolveContentGuard(appsStore.object, new RouterMockup()); + + expect(() => guard.resolve({ params: { appName: 'my-app' } }, {})).toThrow('Route must contain schema name.'); + }); + + it('should throw if route does not contain content id', () => { + const guard = new ResolveContentGuard(appsStore.object, new RouterMockup()); + + expect(() => guard.resolve({ params: { appName: 'my-app', schemaName: 'my-schema' } }, {})).toThrow('Route must contain content id.'); }); it('should navigate to 404 page if schema is not found', (done) => { diff --git a/src/Squidex/app/shared/guards/resolve-content.guard.ts b/src/Squidex/app/shared/guards/resolve-content.guard.ts index ff5ad1188..951a3df9a 100644 --- a/src/Squidex/app/shared/guards/resolve-content.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-content.guard.ts @@ -8,7 +8,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; -import { allParameters } from 'framework'; +import { allParams } from 'framework'; import { ContentDto, ContentsService } from './../services/contents.service'; @@ -21,7 +21,7 @@ export class ResolveContentGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const params = allParameters(route); + const params = allParams(route); const appName = params['appName']; diff --git a/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts index 3b9dfee16..b649f467b 100644 --- a/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts +++ b/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts @@ -31,10 +31,16 @@ describe('ResolvePublishedSchemaGuard', () => { schemasService = Mock.ofType(SchemasService); }); - it('should throw if route does not contain parameter', () => { + it('should throw if route does not contain app name', () => { const guard = new ResolvePublishedSchemaGuard(schemasService.object, new RouterMockup()); - expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app and schema name.'); + expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app name.'); + }); + + it('should throw if route does not contain schema name', () => { + const guard = new ResolvePublishedSchemaGuard(schemasService.object, new RouterMockup()); + + expect(() => guard.resolve({ params: { appName: 'my-app' } }, {})).toThrow('Route must contain schema name.'); }); it('should navigate to 404 page if schema is not found', (done) => { diff --git a/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts b/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts index b8b8594ed..dbd0cacc3 100644 --- a/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts @@ -8,7 +8,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; -import { allParameters } from 'framework'; +import { allParams } from 'framework'; import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; @@ -21,7 +21,7 @@ export class ResolvePublishedSchemaGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const params = allParameters(route); + const params = allParams(route); const appName = params['appName']; diff --git a/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts index aaacd1ad7..2a8c37b5a 100644 --- a/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts +++ b/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts @@ -31,10 +31,16 @@ describe('ResolveSchemaGuard', () => { schemasService = Mock.ofType(SchemasService); }); - it('should throw if route does not contain parameter', () => { + it('should throw if route does not contain app name', () => { const guard = new ResolveSchemaGuard(schemasService.object, new RouterMockup()); - expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app and schema name.'); + expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app name.'); + }); + + it('should throw if route does not contain schema name', () => { + const guard = new ResolveSchemaGuard(schemasService.object, new RouterMockup()); + + expect(() => guard.resolve({ params: { appName: 'my-app' } }, {})).toThrow('Route must contain schema name.'); }); it('should navigate to 404 page if schema is not found', (done) => { diff --git a/src/Squidex/app/shared/guards/resolve-schema.guard.ts b/src/Squidex/app/shared/guards/resolve-schema.guard.ts index 161916ff2..adfeac533 100644 --- a/src/Squidex/app/shared/guards/resolve-schema.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-schema.guard.ts @@ -8,7 +8,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; -import { allParameters } from 'framework'; +import { allParams } from 'framework'; import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; @@ -21,7 +21,7 @@ export class ResolveSchemaGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const params = allParameters(route); + const params = allParams(route); const appName = params['appName']; diff --git a/src/Squidex/app/shared/guards/resolve-user.guard.ts b/src/Squidex/app/shared/guards/resolve-user.guard.ts index 0d331e5bf..dba6b79b1 100644 --- a/src/Squidex/app/shared/guards/resolve-user.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-user.guard.ts @@ -8,7 +8,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; -import { allParameters } from 'framework'; +import { allParams } from 'framework'; import { UserDto, UserManagementService } from './../services/users.service'; @@ -21,7 +21,7 @@ export class ResolveUserGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const params = allParameters(route); + const params = allParams(route); const userId = params['userId']; diff --git a/src/Squidex/app/shared/services/contents.service.spec.ts b/src/Squidex/app/shared/services/contents.service.spec.ts index e4fae773b..728fddd98 100644 --- a/src/Squidex/app/shared/services/contents.service.spec.ts +++ b/src/Squidex/app/shared/services/contents.service.spec.ts @@ -110,6 +110,29 @@ describe('ContentsService', () => { authService.verifyAll(); }); + it('should append ids to get request with ids', () => { + authService.setup(x => x.authGet('http://service/p/api/content/my-app/my-schema?nonPublished=true&hidden=true&$top=17&$skip=13&ids=id1,id2')) + .returns(() => Observable.of( + new Response( + new ResponseOptions({ + body: { + total: 10, + items: [] + } + }) + ) + )) + .verifiable(Times.once()); + + let contents: ContentsDto | null = null; + + contentsService.getContents('my-app', 'my-schema', 17, 13, null, ['id1', 'id2']).subscribe(result => { + contents = result; + }).unsubscribe(); + + authService.verifyAll(); + }); + it('should append query to get request as plain query string', () => { authService.setup(x => x.authGet('http://service/p/api/content/my-app/my-schema?nonPublished=true&hidden=true&$filter=my-filter&$top=17&$skip=13')) .returns(() => Observable.of( diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 492aa4f8c..16f450b57 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -191,6 +191,16 @@ describe('SchemasService', () => { properties: { fieldType: 'Assets' } + }, + { + fieldId: 8, + name: 'field8', + isHidden: true, + isDisabled: true, + partitioning: 'language', + properties: { + fieldType: 'References' + } } ] } @@ -217,7 +227,8 @@ describe('SchemasService', () => { new FieldDto(4, 'field4', true, true, 'language', createProperties('DateTime')), new FieldDto(5, 'field5', true, true, 'language', createProperties('Json')), new FieldDto(6, 'field6', true, true, 'language', createProperties('Geolocation')), - new FieldDto(7, 'field7', true, true, 'language', createProperties('Assets')) + new FieldDto(7, 'field7', true, true, 'language', createProperties('Assets')), + new FieldDto(8, 'field8', true, true, 'language', createProperties('References')) ])); authService.verifyAll();