Skip to content

Commit

Permalink
Merge pull request #295 from MauroDataMapper/feature/gh-274
Browse files Browse the repository at this point in the history
gh-274 Load theme settings from Mauro
  • Loading branch information
pjmonks authored Mar 1, 2023
2 parents 4771f1e + 4191247 commit 91f143b
Show file tree
Hide file tree
Showing 10 changed files with 341 additions and 88 deletions.
10 changes: 10 additions & 0 deletions src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ import {
import { ComponentHarness, setupTestModuleForComponent } from './testing/testing.helpers';
import { createDataElementSearchServiceStub } from './testing/stubs/data-element-search.stub';
import { DataElementSearchService } from './data-explorer/data-element-search.service';
import { createThemeServiceStub } from './testing/stubs/theme.stub';
import { defaultTheme, ThemeService } from './shared/theme.service';
import { of } from 'rxjs';

describe('AppComponent', () => {
let harness: ComponentHarness<AppComponent>;
const dataRequestsStub = createDataRequestsServiceStub();
const endpointsStub: MdmEndpointsServiceStub = createMdmEndpointsStub();
const dataElementSearchStub = createDataElementSearchServiceStub();
const themesStub = createThemeServiceStub();

themesStub.loadTheme.mockImplementation(() => of(defaultTheme));

beforeEach(async () => {
harness = await setupTestModuleForComponent(AppComponent, {
Expand All @@ -63,6 +69,10 @@ describe('AppComponent', () => {
provide: DataElementSearchService,
useValue: dataElementSearchStub,
},
{
provide: ThemeService,
useValue: themesStub,
},
],
});
});
Expand Down
7 changes: 4 additions & 3 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,17 @@ export class AppComponent implements OnInit, OnDestroy {
private userIdle: UserIdleService,
private error: ErrorService,
private themes: ThemeService
) {}
) {
// Load the theme into the DOM as the first thing to do
this.themes.loadTheme().subscribe((theme) => this.themes.applyTheme(theme));
}

@HostListener('window:mousemove', ['$event'])
onMouseMove() {
this.userIdle.resetTimer();
}

ngOnInit(): void {
this.themes.loadTheme().subscribe((theme) => this.themes.applyTheme(theme));

this.broadcast
.on<HttpErrorResponse>('http-application-offline')
.pipe(takeUntil(this.unsubscribe$))
Expand Down
14 changes: 14 additions & 0 deletions src/app/mauro/mauro.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,17 @@ export const isDataClass = (

export const isDataElement = (item: DataClass | DataElement): item is DataElement =>
item.domainType === CatalogueItemDomainType.DataElement;

export interface KeyValueIdentifier {
id: Uuid;
key: string;
value: string;
}

export const getKviValue = (
items: KeyValueIdentifier[],
key: string,
defaultValue: string
) => {
return items.find((i) => i.key === key)?.value ?? defaultValue;
};
14 changes: 14 additions & 0 deletions src/app/mauro/plugins/plugin-research.resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,18 @@ export class MdmPluginResearchResource extends MdmResource {
const url = `${this.apiEndpoint}/explorer/rootDataModel`;
return this.simpleGet(url, query, options);
}

/**
* `HTTP GET` - Gets the theme settings configured via API properties.
*
* @param query Optional query parameters, if required.
* @param options Optional REST handler parameters, if required.
* @returns The result of the `GET` request:
*
* `200 OK` - will return a paged set of key/value pairs.
*/
theme(query?: QueryParameters, options?: RequestSettings) {
const url = `${this.apiEndpoint}/explorer/theme`;
return this.simpleGet(url, query, options);
}
}
8 changes: 8 additions & 0 deletions src/app/mauro/research-plugin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import {
DataModelDetailResponse,
FolderDetail,
FolderDetailResponse,
MdmIndexResponse,
Uuid,
} from '@maurodatamapper/mdm-resources';
import { map, Observable, switchMap } from 'rxjs';
import { KeyValueIdentifier } from './mauro.types';
import { MdmEndpointsService } from './mdm-endpoints.service';
import {
PluginResearchContactPayload,
Expand Down Expand Up @@ -67,4 +69,10 @@ export class ResearchPluginService {
.rootDataModel()
.pipe(map((response: FolderDetailResponse) => response.body));
}

theme(): Observable<KeyValueIdentifier[]> {
return this.endpoints.pluginResearch
.theme()
.pipe(map((response: MdmIndexResponse<KeyValueIdentifier>) => response.body.items));
}
}
170 changes: 99 additions & 71 deletions src/app/shared/theme.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ SPDX-License-Identifier: Apache-2.0
*/
import { setupTestModuleForService } from '../testing/testing.helpers';
import { expect } from '@jest/globals';
import { ThemeService } from './theme.service';
import { defaultTheme, ThemeService } from './theme.service';
import { createResearchPluginServiceStub } from '../testing/stubs/research-plugin.stub';
import { ResearchPluginService } from '../mauro/research-plugin.service';
import { cold, ObservableWithSubscriptions } from 'jest-marbles';

const toHaveCssVariable = (
style: CSSStyleDeclaration,
Expand All @@ -44,93 +47,118 @@ expect.extend({ toHaveCssVariable });
declare module 'expect' {
interface Matchers<R> {
toHaveCssVariable(name: string, expected: string): R;
toBeObservable(observable: ObservableWithSubscriptions): void;
}
}

describe('ThemeService', () => {
let service: ThemeService;
const researchPluginStub = createResearchPluginServiceStub();

beforeEach(() => {
service = setupTestModuleForService(ThemeService);
service = setupTestModuleForService(ThemeService, {
providers: [
{
provide: ResearchPluginService,
useValue: researchPluginStub,
},
],
});
});

it('should be created', () => {
expect(service).toBeTruthy();
});

// TODO: write tests for loadTheme() when actively loading theme data from server
describe('load theme', () => {
it('should return the default theme if unable to fetch from server', () => {
researchPluginStub.theme.mockImplementationOnce(() =>
cold('#', null, new Error('Server error'))
);

const expectCssVariable = (name: string, expected: string) => {
expect(document.documentElement.style).toHaveCssVariable(name, expected);
};
const expected$ = cold('(a|)', { a: defaultTheme });
const actual$ = service.loadTheme();
expect(actual$).toBeObservable(expected$);
});

describe('apply theme', () => {
const theme = {
material: {
colors: {
primary: '#19381f',
accent: '#cdb980',
warn: '#a5122a',
},
// Default typography settings taken from
// https://github.com/angular/components/blob/main/src/material/core/typography/_typography.scss
typography: {
fontFamily: 'Roboto, "Helvetica Neue", sans-serif',
body1: {
fontSize: '14px',
lineHeight: '20px',
fontWeight: 400,
},
body2: {
fontSize: '14px',
lineHeight: '24px',
fontWeight: 500,
},
headline: {
fontSize: '24px',
lineHeight: '32px',
fontWeight: 400,
},
title: {
fontSize: '20px',
lineHeight: '32px',
fontWeight: 500,
},
subheading2: {
fontSize: '16px',
lineHeight: '28px',
fontWeight: 400,
},
subheading1: {
fontSize: '15px',
lineHeight: '24px',
fontWeight: 400,
},
button: {
fontSize: '14px',
lineHeight: '14px',
fontWeight: 500,
it('should return a non-default theme from the server and merged with defaults', () => {
// Arrange
const primaryColor = '#ff0000';
const accentColor = '#00ff00';
const warnColor = '#0000ff';
const fontFamily = 'Comic Sans';
const buttonTypeSettings = ['24px', '16px', '700'];
const body1TypeSettings = ['32px', '12px', '500'];
const pageColor = '#f0f0f0';

const changes = {
'material.colors.primary': primaryColor,
'material.colors.accent': accentColor,
'material.colors.warn': warnColor,
'material.typography.fontfamily': fontFamily,
'material.typography.button': buttonTypeSettings.join(', '), // Intentional: test with ", " separator
'material.typography.bodyone': body1TypeSettings.join(','), // Intentional: test with "," separator (without space)
'contrastcolors.page': pageColor,
};

researchPluginStub.theme.mockImplementationOnce(() => {
const items = Object.entries(changes).map(([key, value]) => {
return {
id: '123',
key,
value,
};
});
return cold('--a|', { a: items });
});

// Build an expected theme, starting with the default as a base and
// making some adjustments to show it's not entirely the same
const expectedTheme = {
...defaultTheme,
material: {
colors: {
primary: primaryColor,
accent: accentColor,
warn: warnColor,
},
input: {
fontSize: 'inherit',
fontWeight: 400,
typography: {
...defaultTheme.material.typography,
fontFamily,
button: {
fontSize: buttonTypeSettings[0],
lineHeight: buttonTypeSettings[1],
fontWeight: buttonTypeSettings[2],
},
body1: {
fontSize: body1TypeSettings[0],
lineHeight: body1TypeSettings[1],
fontWeight: body1TypeSettings[2],
},
},
},
},
regularColors: {
hyperlink: '#003752',
requestCount: '#ffe603',
},
contrastColors: {
page: '#fff',
unsentRequest: '#68d4ca',
submittedRequest: '#008bce',
classRow: '#c4c4c4',
},
};
contrastColors: {
...defaultTheme.contrastColors,
page: pageColor,
},
};

// Act
const actual$ = service.loadTheme();

// Assert
const expected$ = cold('--a|', { a: expectedTheme });
expect(actual$).toBeObservable(expected$);
});
});

const expectCssVariable = (name: string, expected: string) => {
expect(document.documentElement.style).toHaveCssVariable(name, expected);
};

describe('apply theme', () => {
beforeEach(() => {
service.applyTheme(theme);
service.applyTheme(defaultTheme);
});

it('should set the Material "primary" color palette', () => {
Expand Down Expand Up @@ -274,9 +302,9 @@ describe('ThemeService', () => {

expectCssVariable('--theme-color-page', '#ffffff');
expectCssVariable('--theme-color-page-contrast', 'rgba(black, 0.87)');
expectCssVariable('--theme-color-unsentRequest', '#68d4ca');
expectCssVariable('--theme-color-unsentRequest-contrast', 'rgba(black, 0.87)');
expectCssVariable('--theme-color-submittedRequest', '#008bce');
expectCssVariable('--theme-color-unsentRequest', '#008bce');
expectCssVariable('--theme-color-unsentRequest-contrast', 'white');
expectCssVariable('--theme-color-submittedRequest', '#0e8f77');
expectCssVariable('--theme-color-submittedRequest-contrast', 'white');
expectCssVariable('--theme-color-classRow', '#c4c4c4');
expectCssVariable('--theme-color-classRow-contrast', 'rgba(black, 0.87)');
Expand Down
Loading

0 comments on commit 91f143b

Please sign in to comment.