Skip to content

Commit

Permalink
feat: post message events upon placeholder
Browse files Browse the repository at this point in the history
  • Loading branch information
cpaulve-1A committed Oct 16, 2023
1 parent 5172fc9 commit 345d547
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ export const selectPlaceholderTemplateEntity = (placeholderId: string) =>
createSelector(selectPlaceholderTemplateState, (state) => state?.entities[placeholderId]);

/**
* Select the ordered rendered templates for a given placeholderId
* Select the ordered rendered placeholder template full data (url, priority etc.) for a given placeholderId
* Return undefined if the placeholder is not found
* Returns {orderedRenderedTemplates: undefined, isPending: true} if any of the request is still pending
*
* @param placeholderId
*/
export const selectPlaceholderRenderedTemplates = (placeholderId: string) => createSelector(
export const selectSortedTemplates = (placeholderId: string) => createSelector(
selectPlaceholderTemplateEntity(placeholderId),
selectPlaceholderRequestState,
(placeholderTemplate, placeholderRequestState) => {
Expand All @@ -52,13 +52,31 @@ export const selectPlaceholderRenderedTemplates = (placeholderId: string) => cre
});
// No need to perform sorting if still pending
if (isPending) {
return {orderedRenderedTemplates: undefined, isPending};
return {orderedTemplates: undefined, isPending};
}
// Sort templates by priority
const orderedRenderedTemplates = templates.sort((template1, template2) => {
const orderedTemplates = templates.sort((template1, template2) => {
return (template2.priority - template1.priority) || 1;
}).map(template => template.renderedTemplate)
.filter(renderedTemplate => !!renderedTemplate);
}).filter(templateData => !!templateData.renderedTemplate);

return {orderedTemplates, isPending};
});

return {orderedRenderedTemplates, isPending};
/**
* Select the ordered rendered templates for a given placeholderId
* Return undefined if the placeholder is not found
* Returns {orderedRenderedTemplates: undefined, isPending: true} if any of the request is still pending
*
* @param placeholderId
*/
export const selectPlaceholderRenderedTemplates = (placeholderId: string) => createSelector(
selectSortedTemplates(placeholderId),
(placeholderData) => {
if (!placeholderData) {
return;
}
return {
orderedRenderedTemplates: placeholderData.orderedTemplates?.map(placeholder => placeholder.renderedTemplate),
isPending: placeholderData.isPending
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import {
ViewEncapsulation
} from '@angular/core';
import {Store} from '@ngrx/store';
import {sendOtterMessage} from '@o3r/core';
import {ReplaySubject, Subscription} from 'rxjs';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
import {PlaceholderTemplateStore, selectPlaceholderRenderedTemplates} from '../../stores/placeholder-template';
import {distinctUntilChanged, map, switchMap} from 'rxjs/operators';
import {PlaceholderTemplateStore, selectSortedTemplates} from '../../stores/placeholder-template';
import {PlaceholderLoadingStatus, PlaceholderLoadingStatusMessage} from './placeholder.interface';

/**
* Placeholder component that is bind to the PlaceholderTemplateStore to display a template based on its ID
* A loading indication can be provided via projection
*
* @example
* <o3r-placeholder id="my-template-id">Is loading ...</o3r-placeholder>
*/
Expand Down Expand Up @@ -47,27 +48,39 @@ export class PlaceholderComponent implements OnInit, OnDestroy {
constructor(private store: Store<PlaceholderTemplateStore>, private cd: ChangeDetectorRef) {
}

private sendMessage(data: PlaceholderLoadingStatus) {
sendOtterMessage<PlaceholderLoadingStatusMessage>('placeholder-loading-status', data, false);
}

/** @inheritdoc */
public ngOnInit() {
this.subscription.add(
this.id$.pipe(
distinctUntilChanged(),
switchMap((id) => this.store.select(selectPlaceholderRenderedTemplates(id)))
).subscribe((templates) => {
if (templates) {
this.isPending = templates.isPending;
const orderedRenderedTemplates = templates.orderedRenderedTemplates;
if (!orderedRenderedTemplates || !orderedRenderedTemplates.length) {
this.template = undefined;
} else {
// Concatenates the list of templates
this.template = orderedRenderedTemplates.join('');
}
} else {
this.isPending = false;
switchMap((id) =>
this.store.select(selectSortedTemplates(id)).pipe(
map((placeholders) => ({
id,
orderedTemplates: placeholders?.orderedTemplates,
isPending: placeholders?.isPending
})),
distinctUntilChanged()
)
)
).subscribe(({id, orderedTemplates, isPending}) => {
this.isPending = isPending;
if (!orderedTemplates?.length) {
this.template = undefined;
} else {
// Concatenates the list of templates
this.template = orderedTemplates.map(placeholder => placeholder.renderedTemplate).join('');
}
this.cd.markForCheck();
this.sendMessage({
status: this.isPending ? 'loading' : 'loaded',
templateIds: orderedTemplates?.map(placeholder => placeholder.rawUrl),
placeholderId: id || 'unknown'
});
})
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {OtterMessageContent} from '@o3r/core';

/**
* Payload of the {@see PlaceholderLoadingStatusMessage}
*
* Describe the state of an identified placeholder: what template is being loaded and whether the load is done
*/
export interface PlaceholderLoadingStatus {
/**
* Loading state
*/
status: 'loading' | 'loaded';
/**
* Ids of the template to be loaded in the placeholder
*/
templateIds?: string[];
/**
* Identify the placeholder under consideration
*/
placeholderId?: string;
}

/**
* Message to describe a placeholder's loading status: the templates to be loaded and the pending status.
*/
export interface PlaceholderLoadingStatusMessage extends PlaceholderLoadingStatus,
OtterMessageContent<'placeholder-loading-status'> {}
91 changes: 79 additions & 12 deletions packages/@o3r/components/src/tools/placeholder/placeholder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ComponentFixture, getTestBed, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
import {Store} from '@ngrx/store';
import {BehaviorSubject, Subject} from 'rxjs';
import {ReplaySubject, Subject} from 'rxjs';
import {PlaceholderComponent} from './placeholder.component';

/**
Expand All @@ -29,18 +29,24 @@ describe('Placeholder component', () => {
}));

let placeholderComponent: ComponentFixture<PlaceholderComponent>;
type TemplatesFromStore = { orderedRenderedTemplates: (string | undefined)[]; isPending: boolean };
type TemplatesFromStore = {
orderedTemplates: {
renderedTemplate: (string | undefined);
rawUrl: string;
}[];
isPending: boolean;
};
let storeContent: Subject<TemplatesFromStore>;
let mockStore: {
dispatch: jest.Mock;
pipe: jest.Mock;
select: jest.Mock;
};
const postMessageMock = jest.spyOn(window, 'postMessage');

beforeEach(async () => {
storeContent = new BehaviorSubject<TemplatesFromStore>({orderedRenderedTemplates: [''], isPending: false});
storeContent = new ReplaySubject<TemplatesFromStore>(1);
mockStore = {
dispatch: jest.fn(),
pipe: jest.fn().mockReturnValue(storeContent),
select: jest.fn().mockReturnValue(storeContent)
};
await TestBed.configureTestingModule({
Expand All @@ -57,11 +63,19 @@ describe('Placeholder component', () => {
}).compileComponents();
});

afterEach(() => {
jest.clearAllMocks();
postMessageMock.mockReset();
});

it('should render the template', () => {
placeholderComponent = TestBed.createComponent(PlaceholderComponent);

storeContent.next({
orderedRenderedTemplates: ['<div>test template</div>'],
orderedTemplates: [{
rawUrl: '/test.json',
renderedTemplate: '<div>test template</div>'
}],
isPending: false
});

Expand All @@ -70,13 +84,28 @@ describe('Placeholder component', () => {

expect(placeholderComponent.nativeElement.children[0].tagName).toBe('DIV');
expect(placeholderComponent.nativeElement.children[0].innerHTML).toEqual('<div>test template</div>');
expect(postMessageMock).toHaveBeenCalledWith({
type: 'otter',
content: {
dataType: 'placeholder-loading-status',
status: 'loaded',
templateIds: ['/test.json'],
placeholderId: 'testPlaceholder'
}
}, '*');
});

it('should render the templates', () => {
placeholderComponent = TestBed.createComponent(PlaceholderComponent);

storeContent.next({
orderedRenderedTemplates: ['<div>test template</div>', '<div>test template 2</div>'],
orderedTemplates: [{
renderedTemplate: '<div>test template</div>',
rawUrl: 'assets/test.json'
}, {
renderedTemplate: '<div>test template 2</div>',
rawUrl: 'assets/test2.json'
}],
isPending: false
});

Expand All @@ -85,6 +114,15 @@ describe('Placeholder component', () => {

expect(placeholderComponent.nativeElement.children[0].tagName).toBe('DIV');
expect(placeholderComponent.nativeElement.children[0].innerHTML).toEqual('<div>test template</div><div>test template 2</div>');
expect(postMessageMock).toHaveBeenCalledWith({
type: 'otter',
content: {
dataType: 'placeholder-loading-status',
status: 'loaded',
templateIds: ['assets/test.json', 'assets/test2.json'],
placeholderId: 'testPlaceholder'
}
}, '*');
});

it('should retrieve new template on ID change', () => {
Expand All @@ -97,6 +135,7 @@ describe('Placeholder component', () => {
placeholderComponent.detectChanges(true);

expect(mockStore.select).toHaveBeenCalledTimes(2);
expect(postMessageMock).not.toHaveBeenCalled();
});

it('isPending status of the placeholder should display the ng-content', () => {
Expand All @@ -111,15 +150,43 @@ describe('Placeholder component', () => {
expect(contentDisplayed.query(By.css('span'))).toBe(null);

// Simulate a call to an url to retrieve a placeholder, Loading... should be displayed
storeContent.next({orderedRenderedTemplates: [''], isPending: true});
storeContent.next({
orderedTemplates: [{
renderedTemplate: '',
rawUrl: '/test-empty.json'
}],
isPending: true
});
testComponentFixture.detectChanges();

expect(contentDisplayed.query(By.css('span')).nativeElement.innerHTML).toBe('Loading...');
expect(postMessageMock).toHaveBeenCalledWith({
type: 'otter',
content: {
dataType: 'placeholder-loading-status',
status: 'loading',
templateIds: ['/test-empty.json'],
placeholderId: 'placeholder1'
}
}, '*');
expect(testComponentFixture.debugElement.query(By.css('span')).nativeElement.innerHTML).toBe('Loading...');

// Simulate the result from the call, setting isPending to false, renderedTemplate should be displayed
storeContent.next({orderedRenderedTemplates: ['This is the rendered template'], isPending: false});
storeContent.next({
orderedTemplates: [{
renderedTemplate: 'This is the rendered template',
rawUrl: '/test.json'
}],
isPending: false
});
testComponentFixture.detectChanges();

expect(postMessageMock).toHaveBeenCalledWith({
type: 'otter',
content: {
dataType: 'placeholder-loading-status',
status: 'loaded',
templateIds: ['/test.json'],
placeholderId: 'placeholder1'
}
}, '*');
expect(contentDisplayed.query(By.css('div')).nativeElement.innerHTML).toBe('This is the rendered template');
});
});

0 comments on commit 345d547

Please sign in to comment.