Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions packages/angular/lib/src/message-peer.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injector } from '@angular/core';
import { Component, inject, Injector } from '@angular/core';
import {
MESSAGE_PEER_CONFIG,
MESSAGE_PEER_CONNECT_OPTIONS,
Expand All @@ -9,6 +9,7 @@ import {
} from './message-peer.service';
import type { Message, PeerConnectionOptions } from '@amadeus-it-group/microfrontends';
import { MessagePeer } from '@amadeus-it-group/microfrontends';
import { TestBed } from '@angular/core/testing';

describe('MessagePeerService', () => {
let service: MessagePeerServiceType<Message>;
Expand All @@ -24,42 +25,42 @@ describe('MessagePeerService', () => {
jest.restoreAllMocks();
});

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

it('should have the correct id', () => {
test('should have the correct id', () => {
expect(service.id).toBe(config.id);
});

it('should register a message', () => {
test('should register a message', () => {
const message = { type: 'new', version: '1.0' };
service.registerMessage(message);
expect(service.knownPeers.get(config.id)).toContain(message);
});

it('should send a message', () => {
test('should send a message', () => {
const message = { type: 'new', version: '1.0' };
const sendSpy = jest.spyOn(MessagePeer.prototype, 'send').mockImplementation();
service.send(message);
expect(sendSpy).toHaveBeenCalledWith(message, undefined);
});

it('should listen for messages', () => {
test('should listen for messages', () => {
const peerId = 'peer-id';
const listenSpy = jest.spyOn(MessagePeer.prototype, 'listen').mockImplementation();
service.listen(peerId);
expect(listenSpy).toHaveBeenCalledWith(peerId);
});

it('should connect to a peer', async () => {
test('should connect to a peer', async () => {
const peerId = 'peer-id';
const connectSpy = jest.spyOn(MessagePeer.prototype, 'connect').mockImplementation();
await service.connect(peerId);
expect(connectSpy).toHaveBeenCalledWith(peerId, undefined);
});

it('should disconnect from a peer', () => {
test('should disconnect from a peer', () => {
const peerId = 'peer-id';
const disconnectSpy = jest.spyOn(MessagePeer.prototype, 'disconnect').mockImplementation();
service.disconnect(peerId);
Expand Down Expand Up @@ -126,6 +127,64 @@ describe('MessagePeerService Interactions', () => {
});
});

describe('MessagePeerService destruction', () => {
@Component({
selector: 'lib-app1',
template: '',
providers: [MessagePeerService, { provide: MESSAGE_PEER_CONFIG, useValue: { id: 'app1' } }],
})
class App1Component {
service = inject(MessagePeerService);
}

@Component({
selector: 'lib-app2',
template: '',
providers: [MessagePeerService, { provide: MESSAGE_PEER_CONFIG, useValue: { id: 'app2' } }],
})
class App2Component {
service = inject(MessagePeerService);
}

test('should disconnect and stop listening on destruction', () => {
jest.spyOn(MessagePeerService.prototype, 'ngOnDestroy');
jest.spyOn(MessagePeerService.prototype, 'disconnect');

const app1 = TestBed.createComponent(App1Component);
const { service: s1 } = app1.componentInstance;

const app2 = TestBed.createComponent(App2Component);
const { service: s2 } = app2.componentInstance;

const messages: any[] = [];
s2.serviceMessages$.subscribe(({ payload }) =>
messages.push({ type: payload.type, version: payload.version }),
);

// connect and handshake
s1.listen();
s2.connect('app1');
expect(messages).toEqual([
{ type: 'handshake', version: '1.0' },
{ type: 'connect', version: '1.0' },
]);

// destroy app1 -> disconnect
messages.length = 0;
app1.destroy();
expect(messages).toEqual([{ type: 'disconnect', version: '1.0' }]);

// try to connect again -> app1 stopped listening
messages.length = 0;
s2.connect('app1');
expect(messages).toEqual([]);
});

afterEach(() => {
jest.restoreAllMocks();
});
});

describe('MessagePeerService DI overrides', () => {
let connectSpy: jest.SpyInstance;
let listenSpy: jest.SpyInstance;
Expand Down
15 changes: 12 additions & 3 deletions packages/angular/lib/src/message-peer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ServiceMessage,
} from '@amadeus-it-group/microfrontends';
import { from, Observable } from 'rxjs';
import { inject, Injectable, InjectionToken } from '@angular/core';
import { inject, Injectable, InjectionToken, OnDestroy } from '@angular/core';

/**
* Interface for the peer service that provides an observable for incoming messages
Expand Down Expand Up @@ -63,8 +63,11 @@ export const MESSAGE_PEER_LISTEN_OPTIONS = new InjectionToken<
* It is essentially just a wrapper around {@link MessagePeer} that integrates with Angular's DI system.
*/
@Injectable({ providedIn: 'root' })
export class MessagePeerService<M extends Message> implements MessagePeerServiceType<M> {
export class MessagePeerService<M extends Message> implements MessagePeerServiceType<M>, OnDestroy {
readonly #peer: MessagePeerType<M>;
#stopListening: () => void = () => {
// noop, will be set later
};
readonly #diConnectOptions = inject(MESSAGE_PEER_CONNECT_OPTIONS, { optional: true });
readonly #diListenOptions = inject(MESSAGE_PEER_LISTEN_OPTIONS, { optional: true });
/**
Expand Down Expand Up @@ -93,6 +96,11 @@ export class MessagePeerService<M extends Message> implements MessagePeerService
this.errors$ = from(this.#peer.errors);
}

ngOnDestroy(): void {
this.disconnect();
this.#stopListening();
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -120,7 +128,8 @@ export class MessagePeerService<M extends Message> implements MessagePeerService
public listen(
filters?: string | PeerConnectionFilter | (string | PeerConnectionFilter)[],
): () => void {
return this.#peer.listen(filters ? filters : this.#diListenOptions || undefined);
this.#stopListening = this.#peer.listen(filters ? filters : this.#diListenOptions || undefined);
return this.#stopListening;
}

/**
Expand Down