diff --git a/angular.json b/angular.json
index dada2fbe23..792424196c 100644
--- a/angular.json
+++ b/angular.json
@@ -116,6 +116,12 @@
"src/assets",
"src/favicon.ico",
"src/manifest.webmanifest"
+ ],
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.spec.ts"
+ }
]
}
},
diff --git a/src/app/app-initializers.ts b/src/app/app-initializers.ts
new file mode 100644
index 0000000000..f5b6cb8ede
--- /dev/null
+++ b/src/app/app-initializers.ts
@@ -0,0 +1,69 @@
+import {
+ APP_INITIALIZER,
+ Injector,
+ ɵcreateInjector as createInjector,
+} from "@angular/core";
+import { ConfigService } from "./core/config/config.service";
+import { RouterService } from "./core/view/dynamic-routing/router.service";
+import { EntityConfigService } from "./core/entity/entity-config.service";
+import { Router } from "@angular/router";
+import { SessionService } from "./core/session/session-service/session.service";
+import { AnalyticsService } from "./core/analytics/analytics.service";
+import { LoginState } from "./core/session/session-states/login-state.enum";
+import { LoggingService } from "./core/logging/logging.service";
+import { environment } from "../environments/environment";
+
+export const appInitializers = {
+ provide: APP_INITIALIZER,
+ useFactory:
+ (
+ injector: Injector,
+ configService: ConfigService,
+ routerService: RouterService,
+ entityConfigService: EntityConfigService,
+ router: Router,
+ sessionService: SessionService,
+ analyticsService: AnalyticsService
+ ) =>
+ async () => {
+ // Re-trigger services that depend on the config when something changes
+ configService.configUpdates.subscribe(() => {
+ routerService.initRouting();
+ entityConfigService.setupEntitiesFromConfig();
+ const url = location.href.replace(location.origin, "");
+ router.navigateByUrl(url, { skipLocationChange: true });
+ });
+
+ // update the user context for remote error logging and tracking and load config initially
+ sessionService.loginState.subscribe((newState) => {
+ if (newState === LoginState.LOGGED_IN) {
+ const username = sessionService.getCurrentUser().name;
+ LoggingService.setLoggingContextUser(username);
+ analyticsService.setUser(username);
+ } else {
+ LoggingService.setLoggingContextUser(undefined);
+ analyticsService.setUser(undefined);
+ }
+ });
+
+ if (environment.production) {
+ analyticsService.init();
+ }
+ if (environment.demo_mode) {
+ const m = await import("./core/demo-data/demo-data.module");
+ await createInjector(m.DemoDataModule, injector)
+ .get(m.DemoDataModule)
+ .publishDemoData();
+ }
+ },
+ deps: [
+ Injector,
+ ConfigService,
+ RouterService,
+ EntityConfigService,
+ Router,
+ SessionService,
+ AnalyticsService,
+ ],
+ multi: true,
+};
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts
index 0bda7e47a3..950c3255ea 100644
--- a/src/app/app.component.spec.ts
+++ b/src/app/app.component.spec.ts
@@ -15,89 +15,39 @@
* along with ndb-core. If not, see .
*/
-import {
- ComponentFixture,
- discardPeriodicTasks,
- fakeAsync,
- flush,
- TestBed,
- waitForAsync,
-} from "@angular/core/testing";
+import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { AppComponent } from "./app.component";
import { AppModule } from "./app.module";
-import { Config } from "./core/config/config";
-import { USAGE_ANALYTICS_CONFIG_ID } from "./core/analytics/usage-analytics-config";
import { environment } from "../environments/environment";
-import { EntityRegistry } from "./core/entity/database-entity.decorator";
-import { Subject } from "rxjs";
import { Database } from "./core/database/database";
-import { UpdatedEntity } from "./core/entity/model/entity-update";
-import { EntityMapperService } from "./core/entity/entity-mapper.service";
-import { mockEntityMapper } from "./core/entity/mock-entity-mapper-service";
-import { SessionType } from "./core/session/session-type";
import { HttpClientTestingModule } from "@angular/common/http/testing";
-import { Angulartics2Matomo } from "angulartics2";
-import { componentRegistry } from "./dynamic-components";
describe("AppComponent", () => {
let component: AppComponent;
let fixture: ComponentFixture;
- let entityUpdates: Subject>;
+ const intervalBefore = jasmine.DEFAULT_TIMEOUT_INTERVAL;
- beforeAll(() => {
- componentRegistry.allowDuplicates();
- });
beforeEach(waitForAsync(() => {
- environment.session_type = SessionType.mock;
- environment.production = false;
- environment.demo_mode = false;
- const entityMapper = mockEntityMapper();
- entityUpdates = new Subject();
- spyOn(entityMapper, "receiveUpdates").and.returnValue(entityUpdates);
-
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
+ environment.demo_mode = true;
TestBed.configureTestingModule({
imports: [AppModule, HttpClientTestingModule],
- providers: [{ provide: EntityMapperService, useValue: entityMapper }],
}).compileComponents();
-
- spyOn(TestBed.inject(EntityRegistry), "add"); // Prevent error with duplicate registration
}));
- function createComponent() {
+ beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
- }
+ }));
- afterEach(() => TestBed.inject(Database).destroy());
+ afterEach(() => {
+ environment.demo_mode = false;
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = intervalBefore;
+ return TestBed.inject(Database).destroy();
+ });
it("should be created", () => {
- createComponent();
expect(component).toBeTruthy();
});
-
- it("should start tracking with config from db", fakeAsync(() => {
- environment.production = true; // tracking is only active in production mode
- const testConfig = new Config(Config.CONFIG_KEY, {
- [USAGE_ANALYTICS_CONFIG_ID]: {
- url: "matomo-test-endpoint",
- site_id: "101",
- },
- });
- entityUpdates.next({ entity: testConfig, type: "new" });
- const angulartics = TestBed.inject(Angulartics2Matomo);
- const startTrackingSpy = spyOn(angulartics, "startTracking");
- window["_paq"] = [];
-
- createComponent();
- flush();
-
- expect(startTrackingSpy).toHaveBeenCalledTimes(1);
- expect(window["_paq"]).toContain([
- "setSiteId",
- testConfig.data[USAGE_ANALYTICS_CONFIG_ID].site_id,
- ]);
-
- discardPeriodicTasks();
- }));
});
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 340d76407c..f78b371f37 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -15,24 +15,7 @@
* along with ndb-core. If not, see .
*/
-import {
- Component,
- Injector,
- ViewContainerRef,
- ɵcreateInjector as createInjector,
-} from "@angular/core";
-import { AnalyticsService } from "./core/analytics/analytics.service";
-import { ConfigService } from "./core/config/config.service";
-import { RouterService } from "./core/view/dynamic-routing/router.service";
-import { EntityConfigService } from "./core/entity/entity-config.service";
-import { SessionService } from "./core/session/session-service/session.service";
-import { ActivatedRoute, Router } from "@angular/router";
-import { environment } from "../environments/environment";
-import { Child } from "./child-dev-project/children/model/child";
-import { School } from "./child-dev-project/schools/model/school";
-import { LoginState } from "./core/session/session-states/login-state.enum";
-import { LoggingService } from "./core/logging/logging.service";
-import { EntityRegistry } from "./core/entity/database-entity.decorator";
+import { Component } from "@angular/core";
/**
* Component as the main entry point for the app.
@@ -42,58 +25,4 @@ import { EntityRegistry } from "./core/entity/database-entity.decorator";
selector: "app-root",
template: "",
})
-export class AppComponent {
- constructor(
- private viewContainerRef: ViewContainerRef, // need this small hack in order to catch application root view container ref
- private analyticsService: AnalyticsService,
- private configService: ConfigService,
- private routerService: RouterService,
- private entityConfigService: EntityConfigService,
- private sessionService: SessionService,
- private activatedRoute: ActivatedRoute,
- private router: Router,
- private entities: EntityRegistry,
- private injector: Injector
- ) {
- this.initBasicServices();
- }
-
- private async initBasicServices() {
- // TODO: remove this after issue #886 now in next release (keep as fallback for one version)
- this.entities.add("Participant", Child);
- this.entities.add("Team", School);
-
- // first register to events
-
- // Re-trigger services that depend on the config when something changes
- this.configService.configUpdates.subscribe(() => {
- this.routerService.initRouting();
- this.entityConfigService.setupEntitiesFromConfig();
- this.router.navigate([], {
- relativeTo: this.activatedRoute,
- queryParamsHandling: "preserve",
- });
- });
-
- // update the user context for remote error logging and tracking and load config initially
- this.sessionService.loginState.subscribe((newState) => {
- if (newState === LoginState.LOGGED_IN) {
- const username = this.sessionService.getCurrentUser().name;
- LoggingService.setLoggingContextUser(username);
- this.analyticsService.setUser(username);
- } else {
- LoggingService.setLoggingContextUser(undefined);
- this.analyticsService.setUser(undefined);
- }
- });
-
- if (environment.production) {
- this.analyticsService.init();
- }
-
- if (environment.demo_mode) {
- const m = await import("./core/demo-data/demo-data.module");
- createInjector(m.DemoDataModule, this.injector);
- }
- }
-}
+export class AppComponent {}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 3e871d9c96..f06a805547 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -78,6 +78,7 @@ import { TodosModule } from "./features/todos/todos.module";
import { SessionService } from "./core/session/session-service/session.service";
import { waitForChangeTo } from "./core/session/session-states/session-utils";
import { LoginState } from "./core/session/session-states/login-state.enum";
+import { appInitializers } from "./app-initializers";
/**
* Main entry point of the application.
@@ -154,6 +155,7 @@ import { LoginState } from "./core/session/session-states/login-state.enum";
}),
deps: [SessionService],
},
+ appInitializers,
],
bootstrap: [AppComponent],
})
diff --git a/src/app/core/demo-data/demo-data.module.spec.ts b/src/app/core/demo-data/demo-data.module.spec.ts
index b7e936785c..dcf043027e 100644
--- a/src/app/core/demo-data/demo-data.module.spec.ts
+++ b/src/app/core/demo-data/demo-data.module.spec.ts
@@ -22,6 +22,7 @@ describe("DemoDataModule", () => {
});
it("should generate the demo data once the module is loaded", fakeAsync(() => {
+ TestBed.inject(DemoDataModule).publishDemoData();
expect(mockEntityMapper.saveAll).not.toHaveBeenCalled();
TestBed.inject(DemoDataModule);
diff --git a/src/app/core/demo-data/demo-data.module.ts b/src/app/core/demo-data/demo-data.module.ts
index 6a27664c86..4c0f9d083a 100644
--- a/src/app/core/demo-data/demo-data.module.ts
+++ b/src/app/core/demo-data/demo-data.module.ts
@@ -40,6 +40,7 @@ import { DemoConfigurableEnumGeneratorService } from "../configurable-enum/demo-
import { DemoPublicFormGeneratorService } from "../../features/public-form/demo-public-form-generator.service";
const demoDataGeneratorProviders = [
+ ...DemoConfigGeneratorService.provider(),
...DemoPermissionGeneratorService.provider(),
...DemoPublicFormGeneratorService.provider(),
...DemoUserGeneratorService.provider(),
@@ -66,8 +67,6 @@ const demoDataGeneratorProviders = [
maxCountAttributes: 5,
}),
...DemoTodoGeneratorService.provider(),
- // keep Demo service last to ensure all entities are already initialized
- ...DemoConfigGeneratorService.provider(),
];
/**
@@ -106,7 +105,9 @@ const demoDataGeneratorProviders = [
exports: [DemoDataGeneratingProgressDialogComponent],
})
export class DemoDataModule {
- constructor(demoDataInitializer: DemoDataInitializerService) {
- demoDataInitializer.run();
+ constructor(private demoDataInitializer: DemoDataInitializerService) {}
+
+ publishDemoData() {
+ return this.demoDataInitializer.run();
}
}
diff --git a/src/app/core/export/query.service.spec.ts b/src/app/core/export/query.service.spec.ts
index a22859ba75..489ac0965a 100644
--- a/src/app/core/export/query.service.spec.ts
+++ b/src/app/core/export/query.service.spec.ts
@@ -18,8 +18,6 @@ import { expectEntitiesToMatch } from "../../utils/expect-entity-data.spec";
import { Database } from "../database/database";
import { Note } from "../../child-dev-project/notes/model/note";
import { genders } from "../../child-dev-project/children/model/genders";
-import { EntityConfigService } from "app/core/entity/entity-config.service";
-import { ConfigService } from "app/core/config/config.service";
import { EventAttendance } from "../../child-dev-project/attendance/model/event-attendance";
import { AttendanceStatusType } from "../../child-dev-project/attendance/model/attendance-status";
import { DatabaseTestingModule } from "../../utils/database-testing.module";
@@ -44,17 +42,12 @@ describe("QueryService", () => {
(i) => i.id === "COACHING_CLASS"
);
- beforeEach(waitForAsync(async () => {
+ beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [DatabaseTestingModule],
});
service = TestBed.inject(QueryService);
- const configService = TestBed.inject(ConfigService);
- const entityConfigService = TestBed.inject(EntityConfigService);
entityMapper = TestBed.inject(EntityMapperService);
- await configService.loadConfig();
- entityConfigService.addConfigAttributes(School);
- entityConfigService.addConfigAttributes(Child);
}));
afterEach(() => TestBed.inject(Database).destroy());
diff --git a/src/app/core/navigation/navigation/navigation.component.spec.ts b/src/app/core/navigation/navigation/navigation.component.spec.ts
index 5ff35ece67..e8d9edd4b0 100644
--- a/src/app/core/navigation/navigation/navigation.component.spec.ts
+++ b/src/app/core/navigation/navigation/navigation.component.spec.ts
@@ -36,10 +36,11 @@ describe("NavigationComponent", () => {
beforeEach(waitForAsync(() => {
mockConfigUpdated = new BehaviorSubject(null);
- mockConfigService = jasmine.createSpyObj(["getConfig"], {
+ mockConfigService = jasmine.createSpyObj(["getConfig", "getAllConfigs"], {
configUpdates: mockConfigUpdated,
});
mockConfigService.getConfig.and.returnValue({ items: [] });
+ mockConfigService.getAllConfigs.and.returnValue([]);
mockUserRoleGuard = jasmine.createSpyObj(["checkRoutePermissions"]);
mockUserRoleGuard.checkRoutePermissions.and.returnValue(true);
diff --git a/src/app/core/session/login/login.component.spec.ts b/src/app/core/session/login/login.component.spec.ts
index 2af9ccf772..42a964424a 100644
--- a/src/app/core/session/login/login.component.spec.ts
+++ b/src/app/core/session/login/login.component.spec.ts
@@ -43,7 +43,10 @@ describe("LoginComponent", () => {
let loader: HarnessLoader;
beforeEach(waitForAsync(() => {
- mockSessionService = jasmine.createSpyObj(["login"], { loginState });
+ mockSessionService = jasmine.createSpyObj(["login", "getCurrentUser"], {
+ loginState,
+ });
+ mockSessionService.getCurrentUser.and.returnValue({ name: "", roles: [] });
TestBed.configureTestingModule({
imports: [LoginComponent, MockedTestingModule],
providers: [
diff --git a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts
index 10ee3e8173..da754978cd 100644
--- a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts
+++ b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts
@@ -11,7 +11,7 @@ import { EntityMapperService } from "../../../core/entity/entity-mapper.service"
import { AlertService } from "../../../core/alerts/alert.service";
import { ProgressDashboardConfig } from "./progress-dashboard-config";
import { MatDialog } from "@angular/material/dialog";
-import { BehaviorSubject, Subject } from "rxjs";
+import { BehaviorSubject, NEVER, Subject } from "rxjs";
import { take } from "rxjs/operators";
import { SessionService } from "../../../core/session/session-service/session.service";
import { SyncState } from "../../../core/session/session-states/sync-state.enum";
@@ -27,7 +27,10 @@ describe("ProgressDashboardComponent", () => {
beforeEach(waitForAsync(() => {
mockSync = new BehaviorSubject(SyncState.UNSYNCED);
- mockSession = jasmine.createSpyObj([], { syncState: mockSync });
+ mockSession = jasmine.createSpyObj([], {
+ syncState: mockSync,
+ loginState: NEVER,
+ });
TestBed.configureTestingModule({
imports: [ProgressDashboardComponent, MockedTestingModule.withState()],
diff --git a/src/app/utils/mocked-testing.module.ts b/src/app/utils/mocked-testing.module.ts
index 399bbca04a..bfa0f44cc0 100644
--- a/src/app/utils/mocked-testing.module.ts
+++ b/src/app/utils/mocked-testing.module.ts
@@ -52,7 +52,11 @@ export const TEST_PASSWORD = "pass";
{ provide: SwRegistrationOptions, useValue: { enabled: false } },
{
provide: AnalyticsService,
- useValue: { eventTrack: () => undefined },
+ useValue: {
+ eventTrack: () => undefined,
+ setUser: () => undefined,
+ init: () => undefined,
+ },
},
{
provide: DatabaseIndexingService,
diff --git a/src/environments/environment.spec.ts b/src/environments/environment.spec.ts
new file mode 100644
index 0000000000..272a2d5770
--- /dev/null
+++ b/src/environments/environment.spec.ts
@@ -0,0 +1,13 @@
+import { SessionType } from "../app/core/session/session-type";
+import { AuthProvider } from "../app/core/session/auth/auth-provider";
+
+export const environment = {
+ production: false,
+ appVersion: "test",
+ repositoryId: "Aam-Digital/ndb-core",
+ demo_mode: false,
+ session_type: SessionType.mock,
+ authenticator: AuthProvider.CouchDB,
+ account_url: "https://accounts.aam-digital.net",
+ email: undefined,
+};