Skip to content

Commit

Permalink
Add settings.js and SettingsService #40
Browse files Browse the repository at this point in the history
  • Loading branch information
hupf committed Apr 29, 2019
1 parent 8b27f49 commit 584f6b9
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ testem.log
# System Files
.DS_Store
Thumbs.db

# Project-specific
/src/settings.js
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,30 @@ JavaScript Web Modul zu Abbildung des «Absenzenverwaltung» Prozesses mit dem C
TODO:

- Latest build
- Configuration
- Prerequisites (localStorage values etc.), usage of `index.html`

Um die Absenzverwaltung auf einer Webseite zu integrieren, muss aus der `index.html`-Datei folgendes übernommen werden:

```
<head>
<script src="settings.js"></script>
<link rel="stylesheet" href="styles.xyz.css"></head>
</head>
```

Und (alle `<script>`-Tags übernehmen):

```
<body>
<erz-app></erz-app>
<script type="text/javascript" src="runtime.xyz.js"></script>
...
<script type="text/javascript" src="main.xyz.js"></script>
</body>
```

Weiter muss die Datei `settings.example.js` nach `settings.js` umbenannt werden und deren Inhalt angepasst werden.

## Entwicklung

- Allgemeine Aspekte sind im [Wiki](https://github.com/erz-mba-fbi/absenzenmanagement/wiki) dokumentiert
Expand Down
9 changes: 8 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"],
"assets": [
"src/favicon.ico",
"src/settings.js",
"src/settings.example.js",
"src/assets",
{ "glob": "README.md", "input": ".", "output": "." },
{ "glob": "**/*.md", "input": "doc", "output": "doc" }
],
"styles": ["src/styles.css"],
"scripts": [],
"es5BrowserSupport": true
Expand Down
89 changes: 89 additions & 0 deletions src/app/shared/settings.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { TestBed, fakeAsync, tick } from '@angular/core/testing';

import { SettingsService, Settings } from './settings.service';

describe('SettingsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({});
});

describe('.settings$', () => {
let settingsMock: Settings;
let next: jasmine.Spy;
let error: jasmine.Spy;
let complete: jasmine.Spy;
beforeEach(() => {
settingsMock = { apiUrl: 'https://example.com/api' };

next = jasmine.createSpy('next');
error = jasmine.createSpy('error');
complete = jasmine.createSpy('complete');
});

afterEach(() => resetSettings());

it('emits settings object', () => {
setSettings(settingsMock);
const service: SettingsService = TestBed.get(SettingsService);

service.settings$.subscribe(next, error, complete);
expect(next).toHaveBeenCalledWith(settingsMock);
expect(error).not.toHaveBeenCalled();
expect(complete).toHaveBeenCalled();
});

it('emits settings object, when available after 2s', fakeAsync(() => {
const service: SettingsService = TestBed.get(SettingsService);
service.settings$.subscribe(next, error, complete);
expect(next).not.toHaveBeenCalled();

tick(1000);
expect(next).not.toHaveBeenCalled();
expect(error).not.toHaveBeenCalled();
expect(complete).not.toHaveBeenCalled();

tick(1000);
expect(next).not.toHaveBeenCalled();
expect(error).not.toHaveBeenCalled();
expect(complete).not.toHaveBeenCalled();

setSettings(settingsMock);
tick(1000);
expect(next).toHaveBeenCalledWith(settingsMock);
expect(error).not.toHaveBeenCalled();
expect(complete).toHaveBeenCalled();
}));

it('throws error when settings loading failed or not defined', fakeAsync(() => {
const service: SettingsService = TestBed.get(SettingsService);
service.settings$.subscribe(next, error, complete);
expect(next).not.toHaveBeenCalled();

tick(6000);
expect(next).not.toHaveBeenCalled();
expect(error).toHaveBeenCalled();
expect(error.calls.mostRecent().args[0].toString()).toEqual(
'Error: Settings not available'
);
expect(complete).not.toHaveBeenCalled();
}));
});

describe('apiUrl$', () => {
it('returns apiUrl', () => {
setSettings({ apiUrl: 'https://example.com/api' });
const service: SettingsService = TestBed.get(SettingsService);
const callback = jasmine.createSpy('callback');
service.apiUrl$.subscribe(callback);
expect(callback).toHaveBeenCalledWith('https://example.com/api');
});
});

function setSettings(settings: Settings): void {
(window as any).absenzenmanagement = { settings };
}

function resetSettings(): void {
(window as any).absenzenmanagement = undefined;
}
});
52 changes: 52 additions & 0 deletions src/app/shared/settings.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { defer, of, Observable } from 'rxjs';
import { map, retryWhen, delay, pluck, shareReplay } from 'rxjs/operators';

export interface Settings {
apiUrl: string;
}

const SETTINGS_RETRY_COUNT = 10;
const SETTINGS_RETRY_DELAY = 500;

@Injectable({
providedIn: 'root'
})
export class SettingsService {
settings$ = this.loadSettings();
apiUrl$ = this.settings$.pipe(pluck<Settings, string>('apiUrl'));

private loadSettings(): Observable<Settings> {
return defer(() => of(this.settings)).pipe(
map(settings => {
if (settings == null) {
throw new Error('Settings not available');
}
return settings;
}),
retryWhen(this.retryNotifier$),
shareReplay(1)
);
}

private retryNotifier$(errors$: Observable<any>): Observable<any> {
let retries = 0;
return errors$.pipe(
delay(SETTINGS_RETRY_DELAY),
map(error => {
if (retries++ === SETTINGS_RETRY_COUNT) {
throw error;
}
return error;
})
);
}

private get settings(): Option<Settings> {
return (
((window as any).absenzenmanagement &&
(window as any).absenzenmanagement.settings) ||
null
);
}
}
1 change: 1 addition & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<script src="settings.js"></script>
</head>
<body>
<erz-app></erz-app>
Expand Down
8 changes: 8 additions & 0 deletions src/settings.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Rename this file to settings.js and adjust the settings

window.absenzenmanagement = window.absenzenmanagement || {};

window.absenzenmanagement.settings = {
// API base URL without trailing slash
apiUrl: 'https://eventotest.api'
};

0 comments on commit 584f6b9

Please sign in to comment.