Skip to content

Commit

Permalink
fix(shell/js): Fixed fdc3 DesktopAgent integration (morganstanley#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZKRobi authored Jul 7, 2023
1 parent 6871647 commit a6d8b7c
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 109 deletions.
13 changes: 7 additions & 6 deletions src/shell/js/composeui-fdc3/src/ComposeUIDesktopAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,15 @@ export class ComposeUIDesktopAgent implements DesktopAgent {
reject(new Error("The current channel have not been set."));
return;
}
if (typeof contextType != 'string' || !contextType) {
reject(new Error("The contextType was type of ContextHandler, which would use a deprecated function, please use string or null for contextType!"));
return;

if (contextType !=null && typeof contextType != 'string') {
handler = contextType;
contextType = null;
}

const listener = <ComposeUIListener>await this.currentChannel!.addContextListener(contextType, handler!);
const resultContext = await this.currentChannel!.getCurrentContext(contextType)
listener.latestContext = this.currentChannel!.retrieveCurrentContext(contextType);
const listener = <ComposeUIListener>await this.currentChannel!.addContextListener(contextType ?? null, handler!);
const resultContext = await this.currentChannel!.getCurrentContext(contextType ?? undefined)
listener.latestContext = this.currentChannel!.retrieveCurrentContext(contextType ?? undefined);
if (resultContext != listener.latestContext) {
//TODO: integrationtest
await listener.handleContextMessage();
Expand Down
85 changes: 38 additions & 47 deletions src/shell/js/composeui-fdc3/src/Fdc3ComposeUI.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ComposeUIListener } from './infrastructure/ComposeUIListener';
import { ComposeUIDesktopAgent } from './ComposeUIDesktopAgent';
import { ComposeUITopic } from './infrastructure/ComposeUITopic';
import { Channel, ChannelError, Context } from '@finos/fdc3';
import { Fdc3ContextMessage } from './infrastructure/messages/Fdc3ContextMessage';
import { Fdc3GetCurrentContextRequest } from './infrastructure/messages/Fdc3GetCurrentContextRequest';
import { Fdc3FindChannelRequest } from './infrastructure/messages/Fdc3FindChannelRequest';

Expand All @@ -32,7 +31,6 @@ const testInstrument = {
ticker: 'AAPL'
}
};
const dummyTopic= "dummyTopic";
const contextMessageHandlerMock = jest.fn((something) => {
return "dummy";
});
Expand All @@ -51,30 +49,28 @@ describe('Tests for ComposeUIChannel implementation API', () => {
unregisterEndpoint: jest.fn(() => { return Promise.resolve() }),
registerService: jest.fn(() => { return Promise.resolve() }),
unregisterService: jest.fn(() => { return Promise.resolve() }),
invoke: jest.fn(() => { return Promise.resolve(JSON.stringify({context: "", payload: `${JSON.stringify(new Fdc3ContextMessage(dummyChannelId, dummyContext))}` })) })
invoke: jest.fn(() => { return Promise.resolve(JSON.stringify(dummyContext))})
}
});

it('broadcast will call messageRouters publish method', async() => {
const testChannel = new ComposeUIChannel(dummyTopic, "user", messageRouterClient);
const testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClient);
await testChannel.broadcast(testInstrument);
expect(messageRouterClient.publish).toHaveBeenCalledTimes(1);
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(), JSON.stringify(new Fdc3ContextMessage("dummyTopic", testInstrument)));
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId,"user"), JSON.stringify(testInstrument));
});

it('broadcast will set the lastContext to test instrument', async() => {
const testChannel = new ComposeUIChannel(dummyTopic, "user", messageRouterClient);
const testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClient);
await testChannel.broadcast(testInstrument);
const resultContext = await testChannel.getCurrentContext();
expect(messageRouterClient.publish).toHaveBeenCalledTimes(1);
expect(messageRouterClient.invoke).toHaveBeenCalledTimes(1);
expect(messageRouterClient.invoke).toHaveBeenCalledWith(ComposeUITopic.getCurrentContext(dummyTopic, testChannel.type), JSON.stringify(new Fdc3GetCurrentContextRequest(undefined)));
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(), JSON.stringify(new Fdc3ContextMessage(dummyTopic, testInstrument)));
expect(resultContext).toMatchObject({id : dummyChannelId, context: dummyContext});
const resultContext = await testChannel.retrieveCurrentContext();
expect(messageRouterClient.publish).toHaveBeenCalledTimes(1);
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument));
expect(resultContext).toMatchObject(testInstrument);
});

it('getCurrentContext will result the lastContext', async() => {
const testChannel = new ComposeUIChannel(dummyTopic, "user", messageRouterClient);
const testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClient);
await testChannel.broadcast(testInstrument);
const testInstrument2 = {
type: 'fdc3.instrument',
Expand All @@ -83,37 +79,34 @@ describe('Tests for ComposeUIChannel implementation API', () => {
}
};
await testChannel.broadcast(testInstrument2);
const resultContext = await testChannel.getCurrentContext();
const resultContextWithContextType = await testChannel.getCurrentContext(testInstrument2.type);
expect(messageRouterClient.invoke).toBeCalledTimes(2);
const resultContext = await testChannel.retrieveCurrentContext();
const resultContextWithContextType = await testChannel.retrieveCurrentContext(testInstrument2.type);
expect(messageRouterClient.publish).toBeCalledTimes(2);
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(), JSON.stringify(new Fdc3ContextMessage(dummyTopic, testInstrument)));
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(), JSON.stringify(new Fdc3ContextMessage(dummyTopic, testInstrument2)));
expect(messageRouterClient.invoke).toHaveBeenCalledWith(ComposeUITopic.getCurrentContext(dummyTopic, testChannel.type), JSON.stringify(new Fdc3GetCurrentContextRequest(undefined)));
expect(messageRouterClient.invoke).toHaveBeenCalledWith(ComposeUITopic.getCurrentContext(dummyTopic, testChannel.type), JSON.stringify(new Fdc3GetCurrentContextRequest(testInstrument2.type)));
expect(resultContext).toMatchObject({id : dummyChannelId, context: dummyContext});
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument));
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument2));
expect(resultContext).toMatchObject(testInstrument2);
expect(resultContextWithContextType).toMatchObject<Partial<Context>>(testInstrument2);
});

it('getCurrentContext will return null as per the given contextType couldnt be found in the saved contexts', async() =>{
const testChannel = new ComposeUIChannel(dummyTopic, "user", messageRouterClient);
const result = await testChannel.getCurrentContext("dummyContextType");
const testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClient);
const result = await testChannel.retrieveCurrentContext("dummyContextType");
expect(result).toBe(null);
});

it('addContextListener will result a ComposeUIListener', async() => {
const testChannel = new ComposeUIChannel(dummyTopic, "user", messageRouterClient);
const testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClient);
await testChannel.broadcast(testInstrument);
const resultListener = await testChannel.addContextListener('fdc3.instrument', contextMessageHandlerMock);
expect(resultListener).toBeInstanceOf(ComposeUIListener);
expect(contextMessageHandlerMock).toHaveBeenCalledTimes(0); //as per the standard
});

it('addContextListener will fail as per contexType is ContextHandler', async() => {
const testChannel = new ComposeUIChannel(dummyTopic, "user", messageRouterClient);
await expect(testChannel.addContextListener(test => {}))
.rejects
.toThrow("addContextListener with contextType as ContextHandler is deprecated, please use the newer version.");
it('addContextListener will treat contexType is ContextHandler as all types', async() => {
const testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClient);
const resultListener = await testChannel.addContextListener(test => {});
expect(resultListener).toBeInstanceOf(ComposeUIListener);
expect(messageRouterClient.subscribe).toBeCalledTimes(1);
});
});

Expand All @@ -130,55 +123,54 @@ describe('Tests for ComposeUIListener implementation API', () => {
unregisterEndpoint: jest.fn(() => { return Promise.resolve() }),
registerService: jest.fn(() => { return Promise.resolve() }),
unregisterService: jest.fn(() => { return Promise.resolve() }),
invoke: jest.fn(() => { return Promise.resolve(JSON.stringify({context: "", payload: `${JSON.stringify(new Fdc3ContextMessage(dummyChannelId, dummyContext))}` })) })
invoke: jest.fn(() => { return Promise.resolve(JSON.stringify({context: "", payload: `${JSON.stringify(dummyContext)}` })) })
}
});

it('subscribe will call messagerouter subscribe method', async() => {
const testListener = new ComposeUIListener(messageRouterClient, instrument => { console.log(instrument); }, "dummyChannelId", "fdc3.instrument");
const testListener = new ComposeUIListener(messageRouterClient, instrument => { console.log(instrument); }, "dummyChannelId", "user", "fdc3.instrument");
await testListener.subscribe();
expect(messageRouterClient.subscribe).toHaveBeenCalledTimes(1);
//expect(messageRouterClient.subscribe).toHaveBeenCalledWith(ComposeUITopic.broadcast("dummyChannelId"), jest.fn());
});

it('handleContextMessage will trigger the handler', async() => {
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, undefined, "fdc3.instrument");
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, "", "user", "fdc3.instrument");
await testListener.subscribe();
await testListener.handleContextMessage(testInstrument);
expect(contextMessageHandlerMock).toHaveBeenCalledWith(testInstrument);
});

it('handleContextMessage will resolve the LatestContext saved for ComposeUIListener', async() => {
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, undefined, "fdc3.instrument");
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, "", "user", "fdc3.instrument");
await testListener.subscribe();
testListener.latestContext = testInstrument;
await testListener.handleContextMessage();
expect(contextMessageHandlerMock).toHaveBeenCalledWith(testListener.latestContext);
});

it('handleContextMessage will resolve an empty context', async() => {
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, undefined, "fdc3.instrument");
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, "", "user", "fdc3.instrument");
await testListener.subscribe();
await testListener.handleContextMessage();
expect(contextMessageHandlerMock).toHaveBeenCalledWith({type: ""});
});

it('handleContextMessage will be rejected with Error as no handler', async() => {
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, undefined, "fdc3.instrument");
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, "", "user", "fdc3.instrument");
await expect(testListener.handleContextMessage(testInstrument))
.rejects
.toThrow("The current listener is not subscribed.");
});

it('unsubscribe will be true', async() => {
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, "dummyChannelId", "fdc3.instrument");
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, "dummyChannelId", "user", "fdc3.instrument");
await testListener.subscribe();
const resultUnsubscription = testListener.unsubscribe();
expect(resultUnsubscription).toBeTruthy();
});

it('unsubscribe will be false', async() => {
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, undefined, "fdc3.instrument");
const testListener = new ComposeUIListener(messageRouterClient, contextMessageHandlerMock, "", "user", "fdc3.instrument");
const resultUnsubscription = testListener.unsubscribe();
expect(resultUnsubscription).toBeFalsy();
});
Expand All @@ -198,16 +190,16 @@ describe('Tests for ComposeUIDesktopAgent implementation API', () => {
unregisterEndpoint: jest.fn(() => { return Promise.resolve() }),
registerService: jest.fn(() => { return Promise.resolve() }),
unregisterService: jest.fn(() => { return Promise.resolve() }),
invoke: jest.fn(() => { return Promise.resolve(JSON.stringify({context: "", payload: `${JSON.stringify(new Fdc3ContextMessage(dummyChannelId, dummyContext))}` })) })
invoke: jest.fn(() => { return Promise.resolve(JSON.stringify({context: "", payload: `${JSON.stringify(dummyContext)}` })) })
}
});

it('broadcast will trigger publish method of the messageRouter', async() => {
const testDesktopAgent = new ComposeUIDesktopAgent(dummyTopic, messageRouterClient);
await testDesktopAgent.joinUserChannel(dummyTopic);
const testDesktopAgent = new ComposeUIDesktopAgent(dummyChannelId, messageRouterClient);
await testDesktopAgent.joinUserChannel(dummyChannelId);
await testDesktopAgent.broadcast(testInstrument);
expect(messageRouterClient.publish).toBeCalledTimes(1);
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(), JSON.stringify(new Fdc3ContextMessage(dummyTopic, testInstrument)));
expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument));
});

it('broadcast will fail as per the current channel is not defined', async() => {
Expand All @@ -234,13 +226,12 @@ describe('Tests for ComposeUIDesktopAgent implementation API', () => {
expect(messageRouterClient.subscribe).toBeCalledTimes(0);
});

it('addContextListener will fail as per the type of the context type is a function', async() => {
it('addContextListener will treat function context type as all types', async() => {
const testDesktopAgent = new ComposeUIDesktopAgent("dummyPath", messageRouterClient);
await testDesktopAgent.joinUserChannel("dummyPath");
await expect(testDesktopAgent.addContextListener(contextMessageHandlerMock))
.rejects
.toThrow("The contextType was type of ContextHandler, which would use a deprecated function, please use string or null for contextType!");
expect(messageRouterClient.subscribe).toBeCalledTimes(0);
var resultListener = await testDesktopAgent.addContextListener(contextMessageHandlerMock)
expect(resultListener).toBeInstanceOf(ComposeUIListener);
expect(messageRouterClient.subscribe).toBeCalledTimes(1);
});

it('getUserChannels will return the created userchannels', async() => {
Expand Down
32 changes: 14 additions & 18 deletions src/shell/js/composeui-fdc3/src/infrastructure/ComposeUIChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { Channel, Context, ContextHandler, DisplayMetadata, Listener } from "@fi
import { MessageRouter, TopicMessage } from "@morgan-stanley/composeui-messaging-client";
import { ChannelType } from "./ChannelType";
import { ComposeUIListener } from "./ComposeUIListener";
import { Fdc3ContextMessage } from "./messages/Fdc3ContextMessage";
import { Fdc3GetCurrentContextRequest } from "./messages/Fdc3GetCurrentContextRequest";
import { ComposeUITopic } from "./ComposeUITopic";

Expand All @@ -39,9 +38,8 @@ export class ComposeUIChannel implements Channel {
//Setting the last published context message.
this.lastContexts.set(context.type, context);
this.lastContext = context;
const message = new Fdc3ContextMessage(this.id, context);
const topic = ComposeUITopic.broadcast();
await this.messageRouterClient.publish(topic, JSON.stringify(message));
const topic = ComposeUITopic.broadcast(this.id, this.type);
await this.messageRouterClient.publish(topic, JSON.stringify(context));
}

//TODO add error
Expand All @@ -50,13 +48,10 @@ export class ComposeUIChannel implements Channel {
const message = JSON.stringify(new Fdc3GetCurrentContextRequest(contextType));
const response = await this.messageRouterClient.invoke(ComposeUITopic.getCurrentContext(this.id, this.type), message);
if (response) {
const topicMessage = <TopicMessage>JSON.parse(response);
if(topicMessage.payload) {
const context = <Context>JSON.parse(topicMessage.payload);
if(context) {
this.lastContext = context;
this.lastContexts.set(context.type, context);
}
const context = <Context>JSON.parse(response);
if(context) {
this.lastContext = context;
this.lastContexts.set(context.type, context);
}
}
resolve(this.retrieveCurrentContext(contextType));
Expand All @@ -80,12 +75,13 @@ export class ComposeUIChannel implements Channel {
public addContextListener(contextType: string | null, handler: ContextHandler): Promise<Listener>;
public addContextListener(handler: ContextHandler): Promise<Listener>;
public async addContextListener(contextType: any, handler?: any): Promise<Listener> {
if (typeof contextType != 'string' && contextType != null) {
throw new Error("addContextListener with contextType as ContextHandler is deprecated, please use the newer version.");
} else {
const listener = new ComposeUIListener(this.messageRouterClient, handler, this.id, contextType);
await listener.subscribe();
return listener;
};
if (contextType != null && typeof contextType != 'string') {
handler = contextType;
contextType = null;
}

const listener = new ComposeUIListener(this.messageRouterClient, handler, this.id, this.type, contextType);
await listener.subscribe();
return listener;
}
}
Loading

0 comments on commit a6d8b7c

Please sign in to comment.