diff --git a/vuu-ui/packages/vuu-data-react/src/hooks/useVuuTables.ts b/vuu-ui/packages/vuu-data-react/src/hooks/useVuuTables.ts index f7c0b7213..854091c6c 100644 --- a/vuu-ui/packages/vuu-data-react/src/hooks/useVuuTables.ts +++ b/vuu-ui/packages/vuu-data-react/src/hooks/useVuuTables.ts @@ -18,9 +18,7 @@ export const useVuuTables = () => { const { tables } = await server.getTableList(); const tableSchemas = buildTables( await Promise.all( - tables.map((tableDescriptor) => - server.getTableSchema(tableDescriptor) - ) + tables.map((vuuTable) => server.getTableSchema(vuuTable)) ) ); setTables(tableSchemas); diff --git a/vuu-ui/packages/vuu-data/src/connection-manager.ts b/vuu-ui/packages/vuu-data/src/connection-manager.ts index 6dc079422..26cea8032 100644 --- a/vuu-ui/packages/vuu-data/src/connection-manager.ts +++ b/vuu-ui/packages/vuu-data/src/connection-manager.ts @@ -267,7 +267,7 @@ const connectedServerAPI: ServerAPI = { ) => asyncRequest(message), getTableList: async () => - asyncRequest({ type: Message.GET_TABLE_LIST }), + asyncRequest({ type: "GET_TABLE_LIST" }), getTableSchema: async (table) => asyncRequest({ diff --git a/vuu-ui/packages/vuu-data/src/server-proxy/messages.ts b/vuu-ui/packages/vuu-data/src/server-proxy/messages.ts index 6c98788bc..0fc1f7e4a 100644 --- a/vuu-ui/packages/vuu-data/src/server-proxy/messages.ts +++ b/vuu-ui/packages/vuu-data/src/server-proxy/messages.ts @@ -5,14 +5,12 @@ export const CLOSE_TREE_SUCCESS = "CLOSE_TREE_SUCCESS"; export const CLOSE_TREE_REJECT = "CLOSE_TREE_REJECT"; export const CREATE_VISUAL_LINK = "CREATE_VISUAL_LINK"; export const CREATE_VP = "CREATE_VP"; -export const CREATE_VP_SUCCESS = "CREATE_VP_SUCCESS"; export const DISABLE_VP = "DISABLE_VP"; export const DISABLE_VP_SUCCESS = "DISABLE_VP_SUCCESS"; export const DISABLE_VP_REJECT = "DISABLE_VP_REJECT"; export const ENABLE_VP = "ENABLE_VP"; export const ENABLE_VP_SUCCESS = "ENABLE_VP_SUCCESS"; export const ENABLE_VP_REJECT = "ENABLE_VP_REJECT"; -export const GET_TABLE_LIST = "GET_TABLE_LIST"; export const GET_TABLE_META = "GET_TABLE_META"; export const GET_VP_VISUAL_LINKS = "GET_VP_VISUAL_LINKS"; export const GET_VIEW_PORT_MENUS = "GET_VIEW_PORT_MENUS"; @@ -25,7 +23,6 @@ export const VIEW_PORT_MENU_REJ = "VIEW_PORT_MENU_REJ"; export const HB = "HB"; export const HB_RESP = "HB_RESP"; export const LOGIN = "LOGIN"; -export const LOGIN_SUCCESS = "LOGIN_SUCCESS"; export const OPEN_TREE_NODE = "OPEN_TREE_NODE"; export const OPEN_TREE_SUCCESS = "OPEN_TREE_SUCCESS"; export const OPEN_TREE_REJECT = "OPEN_TREE_REJECT"; @@ -36,8 +33,6 @@ export const RPC_RESP = "RPC_RESP"; export const MENU_RPC_RESP = "MENU_RPC_RESP"; export const SET_SELECTION = "SET_SELECTION"; export const SET_SELECTION_SUCCESS = "SET_SELECTION_SUCCESS"; -export const TABLE_META_RESP = "TABLE_META_RESP"; -export const TABLE_LIST_RESP = "TABLE_LIST_RESP"; export const TABLE_ROW = "TABLE_ROW"; export const SIZE = "SIZE"; diff --git a/vuu-ui/packages/vuu-data/src/server-proxy/server-proxy.ts b/vuu-ui/packages/vuu-data/src/server-proxy/server-proxy.ts index d91141c74..58c7d2d1b 100644 --- a/vuu-ui/packages/vuu-data/src/server-proxy/server-proxy.ts +++ b/vuu-ui/packages/vuu-data/src/server-proxy/server-proxy.ts @@ -3,7 +3,9 @@ import { ClientToServerMenuRPC, ClientToServerMessage, LinkDescriptorWithLabel, + ServerToClientCreateViewPortSuccess, ServerToClientMessage, + ServerToClientTableList, ServerToClientTableMeta, VuuLinkDescriptor, VuuRpcRequest, @@ -33,8 +35,6 @@ import { OpenDialogAction, ServerProxySubscribeMessage, VuuUIMessageIn, - VuuUIMessageInTableList, - VuuUIMessageInTableMeta, VuuUIMessageOut, VuuUIMessageOutAggregate, VuuUIMessageOutCloseTreeNode, @@ -125,11 +125,15 @@ export class ServerProxy { private authToken = ""; private user = "user"; private pendingLogin?: PendingLogin; - private pendingTableMetaRequests = new Map(); private pendingRequests = new Map(); private sessionId?: string; private queuedRequests: Array = []; - private cachedTableSchemas: Map> = new Map(); + private cachedTableMetaRequests: Map< + string, + Promise + > = new Map(); + private cachedTableSchemas: Map = new Map(); + private tableList: Promise | undefined; constructor(connection: Connection, callback: PostMessageToClientCallback) { this.connection = connection; @@ -188,32 +192,84 @@ export class ServerProxy { public subscribe(message: ServerProxySubscribeMessage) { // guard against subscribe message when a viewport is already subscribed if (!this.mapClientToServerViewport.has(message.viewport)) { - if ( - !this.hasSchemaForTable(message.table) && - // A Session table is never cached - it is limited to a single workflow interaction - // The metadata for a session table is requested even before the subscribe call. - !isSessionTable(message.table) - ) { - info?.( - `subscribe to ${message.table.table}, no metadata yet, request metadata` - ); - const requestId = nextRequestId(); - this.sendMessageToServer( - { type: "GET_TABLE_META", table: message.table }, - requestId - ); - this.pendingTableMetaRequests.set(requestId, message.viewport); - } + const pendingTableSchema = this.getTableMeta(message.table); + // if ( const viewport = new Viewport(message, this.postMessageToClient); this.viewports.set(message.viewport, viewport); - // use client side viewport id as request id, so that when we process the response, + // Use client side viewport id as request id, so that when we process the response, // which will provide the serverside viewport id, we can establish a mapping between // the two - this.sendIfReady( + //TODO handle CREATE_VP error, but server does not send it at the moment + const pendingSubscription = this.awaitResponseToMessage( viewport.subscribe(), - message.viewport, - this.sessionId !== "" + message.viewport ); + const awaitPendingReponses = Promise.all([ + pendingSubscription, + pendingTableSchema, + ]) as Promise< + [ServerToClientCreateViewPortSuccess, TableSchema | undefined] + >; + awaitPendingReponses.then(([subscribeResponse, tableSchema]) => { + const { viewPortId: serverViewportId } = subscribeResponse; + const { status: viewportStatus } = viewport; + + // switch storage key from client viewportId to server viewportId + if (message.viewport !== serverViewportId) { + this.viewports.delete(message.viewport); + this.viewports.set(serverViewportId, viewport); + } + this.mapClientToServerViewport.set(message.viewport, serverViewportId); + + const clientResponse = viewport.handleSubscribed( + subscribeResponse, + tableSchema + ); + if (clientResponse) { + this.postMessageToClient(clientResponse); + if (debugEnabled) { + debug( + `post DataSourceSubscribedMessage to client: ${JSON.stringify( + clientResponse + )}` + ); + } + } + + // In the case of a reconnect, we may have resubscribed a disabled viewport, + // reset the disabled state on server + if (viewport.disabled) { + this.disableViewport(viewport); + } + + if ( + viewportStatus === "subscribing" && + // A session table will never have Visual Links, nor Context Menus + !isSessionTable(viewport.table) + ) { + // If status is "resubscribing", the following is unnecessary + this.sendMessageToServer({ + type: Message.GET_VP_VISUAL_LINKS, + vpId: serverViewportId, + }); + this.sendMessageToServer({ + type: Message.GET_VIEW_PORT_MENUS, + vpId: serverViewportId, + }); + + // Resend requests for links from other viewports already on page, they may be linkable to this viewport + Array.from(this.viewports.entries()) + .filter( + ([id, { disabled }]) => id !== serverViewportId && !disabled + ) + .forEach(([vpId]) => { + this.sendMessageToServer({ + type: Message.GET_VP_VISUAL_LINKS, + vpId, + }); + }); + } + }); } else { error(`spurious subscribe call ${message.viewport}`); } @@ -558,13 +614,33 @@ export class ServerProxy { } else { const { type, requestId } = message; switch (type) { - case "GET_TABLE_LIST": - return this.sendMessageToServer({ type }, requestId); - case "GET_TABLE_META": - return this.sendMessageToServer( - { type, table: message.table }, + case "GET_TABLE_LIST": { + this.tableList ??= this.awaitResponseToMessage( + { type }, requestId - ); + ) as Promise; + this.tableList.then((response) => { + this.postMessageToClient({ + type: "TABLE_LIST_RESP", + tables: response.tables, + requestId, + }); + }); + return; + } + + case "GET_TABLE_META": { + this.getTableMeta(message.table, requestId).then((tableSchema) => { + if (tableSchema) { + this.postMessageToClient({ + type: "TABLE_META_RESP", + tableSchema, + requestId, + }); + } + }); + return; + } case "RPC_CALL": return this.rpcCall(message); default: @@ -577,11 +653,27 @@ export class ServerProxy { ); } + private getTableMeta(table: VuuTable, requestId = nextRequestId()) { + if (isSessionTable(table)) { + return Promise.resolve(undefined); + } + const key = `${table.module}:${table.table}`; + let tableMetaRequest = this.cachedTableMetaRequests.get(key); + if (!tableMetaRequest) { + tableMetaRequest = this.awaitResponseToMessage( + { type: "GET_TABLE_META", table }, + requestId + ) as Promise; + this.cachedTableMetaRequests.set(key, tableMetaRequest); + } + return tableMetaRequest?.then((response) => this.cacheTableMeta(response)); + } + private awaitResponseToMessage( - message: ClientToServerBody + message: ClientToServerBody, + requestId = nextRequestId() ): Promise { return new Promise((resolve, reject) => { - const requestId = nextRequestId(); this.sendMessageToServer(message, requestId); this.pendingRequests.set(requestId, { reject, resolve }); }); @@ -642,7 +734,7 @@ export class ServerProxy { ); break; - case Message.LOGIN_SUCCESS: + case "LOGIN_SUCCESS": if (sessionId) { this.sessionId = sessionId; // we should tear down the pending Login now @@ -654,68 +746,6 @@ export class ServerProxy { break; // TODO login rejected - case Message.CREATE_VP_SUCCESS: - { - const viewport = viewports.get(requestId); - // The clientViewportId was used as requestId for CREATE_VP message. From this point, - // we will key viewports using serverViewPortId and maintain a mapping between client - // and server viewport ids. - if (viewport) { - const { status: viewportStatus } = viewport; - const { viewPortId: serverViewportId } = body; - - if (requestId !== serverViewportId) { - viewports.delete(requestId); - viewports.set(serverViewportId, viewport); - } - this.mapClientToServerViewport.set(requestId, serverViewportId); - const response = viewport.handleSubscribed(body); - if (response) { - this.postMessageToClient(response); - if (debugEnabled) { - debug( - `post DataSourceSubscribedMessage to client: ${JSON.stringify( - response - )}` - ); - } - } - // In the case of a reconnect, we may have resubscribed a disabled viewport, - // reset the disabled state on server - if (viewport.disabled) { - this.disableViewport(viewport); - } - if ( - viewportStatus === "subscribing" && - // A session table will never have Visual Links, nor Context Menus - !isSessionTable(viewport.table) - ) { - // If status is "resubscribing", the following is unnecessary - this.sendMessageToServer({ - type: Message.GET_VP_VISUAL_LINKS, - vpId: serverViewportId, - }); - this.sendMessageToServer({ - type: Message.GET_VIEW_PORT_MENUS, - vpId: serverViewportId, - }); - - // Resend requests for links from other viewports already on page, they may be linkable to this viewport - Array.from(viewports.entries()) - .filter( - ([id, { disabled }]) => id !== serverViewportId && !disabled - ) - .forEach(([vpId]) => { - this.sendMessageToServer({ - type: Message.GET_VP_VISUAL_LINKS, - vpId, - }); - }); - } - } - } - break; - case "REMOVE_VP_SUCCESS": { const viewport = viewports.get(body.viewPortId); @@ -891,43 +921,6 @@ export class ServerProxy { } break; - case Message.TABLE_LIST_RESP: - this.postMessageToClient({ - type: Message.TABLE_LIST_RESP, - tables: body.tables, - requestId, - } as VuuUIMessageInTableList); - break; - - case Message.TABLE_META_RESP: - // This request may have originated from client or may have been made by - // ServerProxy whilst creating a new subscription - { - const tableSchema = this.cacheTableMeta(body); - const clientViewportId = this.pendingTableMetaRequests.get(requestId); - if (clientViewportId) { - this.pendingTableMetaRequests.delete(requestId); - // If the viewport is still stored under clientViewportId, the subscription has not - // yet been acknowledged and client not informed. If the subscription has already - // been acknowledged, the viewport will be stored under serverViewportId; - const viewport = this.viewports.get(clientViewportId); - if (viewport) { - viewport.setTableSchema(tableSchema); - } else { - warn?.( - "Message has come back AFTER CREATE_VP_SUCCESS, what do we do now" - ); - } - } else { - this.postMessageToClient({ - type: Message.TABLE_META_RESP, - tableSchema, - requestId, - } as VuuUIMessageInTableMeta); - } - } - break; - case "VP_VISUAL_LINKS_RESP": { const activeLinkDescriptors = this.getActiveLinks(body.links); @@ -1073,10 +1066,6 @@ export class ServerProxy { } } - private hasSchemaForTable(table: VuuTable) { - return this.cachedTableSchemas.has(`${table.module}:${table.table}`); - } - private cacheTableMeta(messageBody: ServerToClientTableMeta): TableSchema { const { module, table } = messageBody.table; const key = `${module}:${table}`; diff --git a/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts b/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts index 3858fcab2..ec0678bab 100644 --- a/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts +++ b/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts @@ -182,7 +182,6 @@ export class Viewport { private postMessageToClient: (message: DataSourceCallbackMessage) => void; private rowCountChanged = false; private selectedRows: Selection = []; - private tableSchema: TableSchema | null = null; private useBatchMode = true; private lastUpdateStatus: LastUpdateStatus = NO_UPDATE_STATUS; private updateThrottleTimer: number | undefined = undefined; @@ -299,15 +298,18 @@ export class Viewport { } as ClientToServerCreateViewPort; } - handleSubscribed({ - viewPortId, - aggregations, - columns, - filterSpec: filter, - range, - sort, - groupBy, - }: ServerToClientCreateViewPortSuccess) { + handleSubscribed( + { + viewPortId, + aggregations, + columns, + filterSpec: filter, + range, + sort, + groupBy, + }: ServerToClientCreateViewPortSuccess, + tableSchema?: TableSchema + ) { this.serverViewportId = viewPortId; this.status = "subscribed"; this.aggregations = aggregations; @@ -330,7 +332,7 @@ export class Viewport { groupBy, range, sort, - tableSchema: this.tableSchema, + tableSchema, } as DataSourceSubscribedMessage; } @@ -572,10 +574,6 @@ export class Viewport { }; } - setTableSchema(tableSchema: TableSchema) { - this.tableSchema = tableSchema; - } - openTreeNode(requestId: string, message: VuuUIMessageOutOpenTreeNode) { if (this.useBatchMode) { this.batchMode = true; diff --git a/vuu-ui/packages/vuu-data/test/server-proxy-throttle.test.ts b/vuu-ui/packages/vuu-data/test/server-proxy-throttle.test.ts index 0b611d7b9..f7e7b7e7f 100644 --- a/vuu-ui/packages/vuu-data/test/server-proxy-throttle.test.ts +++ b/vuu-ui/packages/vuu-data/test/server-proxy-throttle.test.ts @@ -36,9 +36,9 @@ const mockConnection = { }; describe("ServerProxy 'size-only throttling'", () => { - it("passes a size only message through to UI client", () => { + it("passes a size only message through to UI client", async () => { const postMessageToClient = vi.fn(); - const serverProxy = createServerProxyAndSubscribeToViewport( + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, { connection: mockConnection, diff --git a/vuu-ui/packages/vuu-data/test/server-proxy.test.ts b/vuu-ui/packages/vuu-data/test/server-proxy.test.ts index 0824c8771..bd3d6972a 100644 --- a/vuu-ui/packages/vuu-data/test/server-proxy.test.ts +++ b/vuu-ui/packages/vuu-data/test/server-proxy.test.ts @@ -1,5 +1,5 @@ import "./global-mocks"; -import { describe, expect, vi, it } from "vitest"; +import { beforeEach, describe, expect, vi, it } from "vitest"; import { ServerProxy, TEST_setRequestId, @@ -14,6 +14,8 @@ import { createTableGroupRows, createSubscription, sizeRow, + subscribe, + testSchema, updateTableRow, } from "./test-utils"; import { @@ -30,32 +32,72 @@ const SERVER_MESSAGE_CONSTANTS = { user: "user", }; -const mockConnection = { - send: vi.fn(), - status: "ready" as const, -}; - -const noop = () => undefined; - describe("ServerProxy", () => { + beforeEach(() => { + TEST_setRequestId(1); + }); + describe("subscription", () => { - it("creates Viewport on client subscribe", () => { - const [clientSubscription] = createSubscription(); - const serverProxy = new ServerProxy(mockConnection, noop); - serverProxy.subscribe(clientSubscription); - expect(serverProxy["viewports"].size).toEqual(1); - const { clientViewportId, status } = serverProxy["viewports"].get( - "client-vp-1" - ) as Viewport; - expect(clientViewportId).toEqual("client-vp-1"); - expect(status).toEqual("subscribing"); + it("sends server requests for metadata, links and menus along with subscription", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + await createServerProxyAndSubscribeToViewport(postMessageToClient, { + connection, + }); + + expect(connection.send).toBeCalledTimes(4); + + expect(connection.send).toHaveBeenNthCalledWith(1, { + body: { + table: { module: "TEST", table: "test-table" }, + type: "GET_TABLE_META", + }, + requestId: "1", + ...SERVER_MESSAGE_CONSTANTS, + }); + + expect(connection.send).toHaveBeenNthCalledWith(2, { + body: { + aggregations: [], + columns: ["col-1"], + type: "CREATE_VP", + table: { module: "TEST", table: "test-table" }, + range: { from: 0, to: 10 }, + sort: { sortDefs: [] }, + filterSpec: { filter: "" }, + groupBy: [], + }, + requestId: "client-vp-1", + ...SERVER_MESSAGE_CONSTANTS, + }); + + expect(connection.send).toHaveBeenNthCalledWith(3, { + body: { + type: "GET_VP_VISUAL_LINKS", + vpId: "server-vp-1", + }, + requestId: "2", + ...SERVER_MESSAGE_CONSTANTS, + }); + expect(connection.send).toHaveBeenNthCalledWith(4, { + body: { + type: "GET_VIEW_PORT_MENUS", + vpId: "server-vp-1", + }, + requestId: "3", + ...SERVER_MESSAGE_CONSTANTS, + }); }); - it("initialises Viewport when server ACKS subscription", () => { + it("initialises Viewport when server ACKS subscription", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); - + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); expect(serverProxy["viewports"].size).toEqual(1); expect( serverProxy["mapClientToServerViewport"].get("client-vp-1") @@ -68,15 +110,12 @@ describe("ServerProxy", () => { expect(status).toEqual("subscribed"); }); - it("sends message to client once subscribed", () => { - const callback = vi.fn(); - const [clientSubscription, serverSubscription] = createSubscription(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy.subscribe(clientSubscription); - serverProxy.handleMessageFromServer(serverSubscription); - //TODO cover tableSchema in test - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith({ + it("sends message to client once subscribed", async () => { + const postMessageToClient = vi.fn(); + await createServerProxyAndSubscribeToViewport(postMessageToClient); + + expect(postMessageToClient).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledWith({ aggregations: [], clientViewportId: "client-vp-1", columns: ["col-1", "col-2", "col-3", "col-4"], @@ -89,17 +128,20 @@ describe("ServerProxy", () => { sort: { sortDefs: [], }, - tableSchema: null, + tableSchema: testSchema, type: "subscribed", }); }); }); describe("Data Handling", () => { - it("sends data to client when initial full dataset is received", () => { + it("sends data to client when initial full dataset is received", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); + + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -133,10 +175,13 @@ describe("ServerProxy", () => { ); }); - it("sends data to client once all data for client range is available", () => { + it("sends data to client once all data for client range is available", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); + + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -191,10 +236,11 @@ describe("ServerProxy", () => { }); describe("Scrolling, no buffer", () => { - it("scrolls forward, partial viewport", () => { + it("scrolls forward, partial viewport", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -255,10 +301,11 @@ describe("ServerProxy", () => { }); }); - it("scrolls forward, discrete viewport", () => { + it("scrolls forward, discrete viewport", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -320,10 +367,11 @@ describe("ServerProxy", () => { }); describe("Updates", () => { - it("Updates, no scrolling, only sends updated rows to client", () => { + it("Updates, no scrolling, only sends updated rows to client", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -357,34 +405,21 @@ describe("ServerProxy", () => { }); describe("Buffering data", () => { - it("buffers 10 rows, server sends entire buffer set", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - bufferSize: 10, - }); - + it("buffers 10 rows, server sends entire buffer set", async () => { const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 10, + connection, + } + ); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; - - mockConnection.send.mockClear(); - TEST_setRequestId(1); - - serverProxy.subscribe(clientSubscription1); - - expect(mockConnection.send).toBeCalledTimes(2); - - expect(mockConnection.send).toHaveBeenNthCalledWith(1, { - body: { - table: clientSubscription1.table, - type: "GET_TABLE_META", - }, - requestId: "1", - ...SERVER_MESSAGE_CONSTANTS, - }); - - expect(mockConnection.send).toHaveBeenNthCalledWith(2, { + expect(connection.send).toHaveBeenNthCalledWith(2, { body: { aggregations: [], columns: ["col-1"], @@ -399,8 +434,6 @@ describe("ServerProxy", () => { ...SERVER_MESSAGE_CONSTANTS, }); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); - postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ @@ -433,20 +466,22 @@ describe("ServerProxy", () => { }); }); - it("10 rows in grid, so 11 requested, (render buffer 0), 10 rows in Viewport buffer, page down, narrowing of range by 1 row", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - bufferSize: 10, - to: 11, - }); - + it("10 rows in grid, so 11 requested, (render buffer 0), 10 rows in Viewport buffer, page down, narrowing of range by 1 row", async () => { const postMessageToClient = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; - - serverProxy.subscribe(clientSubscription1); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 10, + connection, + to: 11, + } + ); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenNthCalledWith(2, { body: { aggregations: [], columns: ["col-1"], @@ -464,8 +499,6 @@ describe("ServerProxy", () => { user: "user", }); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); - postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ @@ -526,15 +559,17 @@ describe("ServerProxy", () => { }); }); - it("buffers 10 rows, server sends partial buffer set, enough to fulfill client request, followed by rest", () => { + it("buffers 10 rows, server sends partial buffer set, enough to fulfill client request, followed by rest", async () => { const postMessageToClient = vi.fn(); - const serverProxy = createServerProxyAndSubscribeToViewport( + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, { bufferSize: 10, } ); + postMessageToClient.mockClear(); + serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -577,15 +612,17 @@ describe("ServerProxy", () => { expect(postMessageToClient).toHaveBeenCalledTimes(0); }); - it("buffers 10 rows, server sends partial buffer set, not enough to fulfill client request, followed by rest", () => { + it("buffers 10 rows, server sends partial buffer set, not enough to fulfill client request, followed by rest", async () => { const postMessageToClient = vi.fn(); - const serverProxy = createServerProxyAndSubscribeToViewport( + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, { bufferSize: 10, } ); + postMessageToClient.mockClear(); + serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -636,7 +673,7 @@ describe("ServerProxy", () => { postMessageToClient.mockClear(); - // This will be a buffer top-up only, so no callback + // This will be a buffer top-up only, so no postMessageToClient serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -650,19 +687,23 @@ describe("ServerProxy", () => { }); describe("scrolling, with buffer", () => { - it("scroll to end", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - to: 20, - bufferSize: 100, - }); + it("scroll to end", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 100, + connection, + to: 20, + } + ); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -675,8 +716,8 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); TEST_setRequestId(1); serverProxy.handleMessageFromClient({ @@ -685,9 +726,9 @@ describe("ServerProxy", () => { range: { from: 4975, to: 5000 }, }); - expect(callback).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledWith({ body: { viewPortId: "server-vp-1", type: "CHANGE_VP_RANGE", @@ -712,7 +753,7 @@ describe("ServerProxy", () => { }, }); - expect(callback).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(0); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -722,10 +763,10 @@ describe("ServerProxy", () => { }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -759,17 +800,22 @@ describe("ServerProxy", () => { }); }); - it("returns client range requests from buffer, if available. Calls server when end of buffer is approached", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - bufferSize: 10, - }); - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + it("returns client range requests from buffer, if available. Calls server when end of buffer is approached", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 10, + connection, + } + ); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -779,8 +825,8 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -788,11 +834,11 @@ describe("ServerProxy", () => { range: { from: 2, to: 12 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(0); - expect(callback).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -802,8 +848,8 @@ describe("ServerProxy", () => { ], }); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -811,11 +857,11 @@ describe("ServerProxy", () => { range: { from: 5, to: 15 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(0); - expect(callback).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -826,8 +872,8 @@ describe("ServerProxy", () => { ], }); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); TEST_setRequestId(1); serverProxy.handleMessageFromClient({ @@ -836,11 +882,11 @@ describe("ServerProxy", () => { range: { from: 8, to: 18 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -851,7 +897,7 @@ describe("ServerProxy", () => { ], }); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ user: "user", body: { viewPortId: "server-vp-1", @@ -866,18 +912,17 @@ describe("ServerProxy", () => { }); }); - it("records sent to client when enough data available, client scrolls before initial rows rendered", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - bufferSize: 10, - }); + it("records sent to client when enough data available, client scrolls before initial rows rendered", async () => { + const postMessageToClient = vi.fn(); - // 1) subscribe for rows [0,10] - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 10, + } + ); - callback.mockClear(); + postMessageToClient.mockClear(); // 2) server with responds with just rows [0 ... 4] serverProxy.handleMessageFromServer({ @@ -889,15 +934,15 @@ describe("ServerProxy", () => { }); // 3) Do not have entire set requested by user, so only size is initially returned - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "size-only", type: "viewport-update", clientViewportId: "client-vp-1", size: 100, }); - callback.mockClear(); + postMessageToClient.mockClear(); // 4) now client scrolls, before initial data sent serverProxy.handleMessageFromClient({ @@ -906,7 +951,7 @@ describe("ServerProxy", () => { range: { from: 2, to: 12 }, }); - expect(callback).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(0); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -916,9 +961,9 @@ describe("ServerProxy", () => { }, }); - expect(callback).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(0); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -928,10 +973,10 @@ describe("ServerProxy", () => { }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -950,19 +995,17 @@ describe("ServerProxy", () => { }); }); - it("data sequence is correct when scrolling backward, data arrives from server in multiple batches", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - bufferSize: 10, - }); - // Client requests rows 0..10 with viewport buffersize of 10 - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); + it("data sequence is correct when scrolling backward, data arrives from server in multiple batches", async () => { + const postMessageToClient = vi.fn(); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 10, + } + ); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + postMessageToClient.mockClear(); // This translates into server call for rows 0..20 these are all stored in Viewport cache // and rows 0..10 returned to client @@ -974,7 +1017,7 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); + postMessageToClient.mockClear(); // Client now requests 20..30, with the buffer this translates to 15..35. // We have 0..20 in Viewport cache, so 0..15 will be discarded and 20..30 @@ -1003,9 +1046,9 @@ describe("ServerProxy", () => { rows: [...createTableRows("server-vp-1", 20, 35, 100, 2)], }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); - callback.mockClear(); + postMessageToClient.mockClear(); // Client now requests 12..22 (scrolled backwards) which expands to 7..27 Viewport cache // contains 15..35 so we discard 27..35 and keep 15..27. We can expect 7..15 from server. @@ -1018,7 +1061,7 @@ describe("ServerProxy", () => { type: "setViewRange", range: { from: 12, to: 22 }, }); - expect(callback).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(0); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1041,9 +1084,9 @@ describe("ServerProxy", () => { rows: [...createTableRows("server-vp-1", 13, 15, 100, 3)], }, }); - expect(callback).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(0); - callback.mockClear(); + postMessageToClient.mockClear(); // We get the remaining rows we requested. Viewport cache now contains full 7..27 // and we have all the rows from the client range, so we can take this together with @@ -1055,10 +1098,10 @@ describe("ServerProxy", () => { rows: [...createTableRows("server-vp-1", 7, 13, 100, 4)], }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -1077,15 +1120,22 @@ describe("ServerProxy", () => { }); }); - it("Scrolling with large buffer. Keys are recomputed on each scroll. Calls server when end of buffer is approached", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - bufferSize: 100, - }); + it("Scrolling with large buffer. Keys are recomputed on each scroll. Calls server when end of buffer is approached", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 100, + connection, + } + ); - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1110,8 +1160,8 @@ describe("ServerProxy", () => { to: 10, }); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -1119,11 +1169,11 @@ describe("ServerProxy", () => { range: { from: 12, to: 23 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(0); - expect(callback).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -1142,8 +1192,8 @@ describe("ServerProxy", () => { ], }); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -1151,11 +1201,11 @@ describe("ServerProxy", () => { range: { from: 30, to: 40 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(0); - expect(callback).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -1176,13 +1226,17 @@ describe("ServerProxy", () => { }); describe("synchronising with server", () => { - it("does not spam server when buffer limit reached and server request already in-flight", () => { - TEST_setRequestId(1); + it("does not spam server when buffer limit reached and server request already in-flight", async () => { + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + const postMessageToClient = vi.fn(); // prettier-ignore - const serverProxy = createServerProxyAndSubscribeToViewport( + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, - { bufferSize: 20, connection: mockConnection } + { bufferSize: 20, connection } ); TEST_setRequestId(1); @@ -1197,7 +1251,7 @@ describe("ServerProxy", () => { }); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); // 2) Client requests rows 16..26 . although non-contiguous with previous request, we already have // full client range in viewport buffer. We need to read ahead from server, because we're close to @@ -1208,11 +1262,11 @@ describe("ServerProxy", () => { range: { from: 16, to: 26 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(1); expect(postMessageToClient).toHaveBeenCalledTimes(1); // TODO test for the call to get nmetadata as well - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ user: "user", body: { viewPortId: "server-vp-1", @@ -1227,7 +1281,7 @@ describe("ServerProxy", () => { }); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); // Client requests 17..27 before we have received response to previous request. The // request in-flight already covers this range. We have the data in cache to satisfy @@ -1238,11 +1292,11 @@ describe("ServerProxy", () => { range: { from: 17, to: 27 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(0); expect(postMessageToClient).toHaveBeenCalledTimes(1); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); // client requests 18..28 same deal as above serverProxy.handleMessageFromClient({ @@ -1251,18 +1305,21 @@ describe("ServerProxy", () => { range: { from: 18, to: 28 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(0); expect(postMessageToClient).toHaveBeenCalledTimes(1); }); - it("re-requests data from server even before receiving results", () => { - TEST_setRequestId(1); + it("re-requests data from server even before receiving results", async () => { const postMessageToClient = vi.fn(); - const serverProxy = createServerProxyAndSubscribeToViewport( + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, { bufferSize: 20, - connection: mockConnection, + connection, } ); @@ -1276,7 +1333,7 @@ describe("ServerProxy", () => { }); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); TEST_setRequestId(1); @@ -1288,11 +1345,11 @@ describe("ServerProxy", () => { range: { from: 16, to: 26 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(1); expect(postMessageToClient).toHaveBeenCalledTimes(1); // buffer size is 20 so we will have requested +/- 10 around the client range - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ user: "user", body: { viewPortId: "server-vp-1", @@ -1319,7 +1376,7 @@ describe("ServerProxy", () => { }); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); // 4) client scrolls forward again, before we have received previously requested rows. We already have // a request in flight, so we don't send another. We have all rows client needs. @@ -1329,11 +1386,11 @@ describe("ServerProxy", () => { range: { from: 17, to: 27 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(0); expect(postMessageToClient).toHaveBeenCalledTimes(1); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); TEST_setRequestId(1); @@ -1345,10 +1402,10 @@ describe("ServerProxy", () => { range: { from: 24, to: 34 }, }); - expect(mockConnection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(1); expect(postMessageToClient).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ user: "user", body: { viewPortId: "server-vp-1", @@ -1365,15 +1422,16 @@ describe("ServerProxy", () => { }); describe("growing and shrinking rowset (Orders)", () => { - it("initializes with rowset that does not fill client viewport", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - to: 20, - bufferSize: 100, - }); + it("initializes with rowset that does not fill client viewport", async () => { const postMessageToClient = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 100, + to: 20, + } + ); postMessageToClient.mockClear(); @@ -1410,15 +1468,17 @@ describe("ServerProxy", () => { }); }); - it("gradually reduces, then grows viewport", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - to: 20, - bufferSize: 100, - }); - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + it("gradually reduces, then grows viewport", async () => { + const postMessageToClient = vi.fn(); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 100, + to: 20, + } + ); + + postMessageToClient.mockClear(); const timeNow = Date.now(); console.log(`time now ${timeNow}`); @@ -1435,7 +1495,7 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1456,9 +1516,9 @@ describe("ServerProxy", () => { }, }); - // callbacks will be size only - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith({ + // postMessageToClients will be size only + expect(postMessageToClient).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "size-only", type: "viewport-update", clientViewportId: "client-vp-1", @@ -1467,7 +1527,7 @@ describe("ServerProxy", () => { vi.setSystemTime(timeNow + 10); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -1475,8 +1535,8 @@ describe("ServerProxy", () => { rows: [sizeRow("server-vp-1", 8)], }, }); - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledWith({ type: "viewport-update", mode: "size-only", clientViewportId: "client-vp-1", @@ -1485,7 +1545,7 @@ describe("ServerProxy", () => { vi.setSystemTime(timeNow + 20); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -1493,15 +1553,15 @@ describe("ServerProxy", () => { rows: [sizeRow("server-vp-1", 1)], }, }); - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "size-only", type: "viewport-update", clientViewportId: "client-vp-1", size: 1, }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -1510,15 +1570,15 @@ describe("ServerProxy", () => { }, }); // fails intermittent;y with 0 - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "size-only", type: "viewport-update", clientViewportId: "client-vp-1", size: 0, }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -1529,9 +1589,9 @@ describe("ServerProxy", () => { ], }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "update", // WRONG type: "viewport-update", clientViewportId: "client-vp-1", @@ -1541,7 +1601,7 @@ describe("ServerProxy", () => { ], }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -1552,9 +1612,9 @@ describe("ServerProxy", () => { ], }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "update", // WRONG type: "viewport-update", clientViewportId: "client-vp-1", @@ -1564,7 +1624,7 @@ describe("ServerProxy", () => { ], }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, body: { @@ -1575,9 +1635,9 @@ describe("ServerProxy", () => { ], }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "update", // WRONG type: "viewport-update", clientViewportId: "client-vp-1", @@ -1593,17 +1653,22 @@ describe("ServerProxy", () => { }); describe("selection", () => { - it("single select", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription({ - to: 20, - }); + it("single select", async () => { const postMessageToClient = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + const connection = { + send: vi.fn(), + status: "ready" as const, + }; - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + to: 20, + } + ); + + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1618,7 +1683,7 @@ describe("ServerProxy", () => { TEST_setRequestId(1); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); // prettier-ignore serverProxy.handleMessageFromClient({ @@ -1629,8 +1694,8 @@ describe("ServerProxy", () => { expect(postMessageToClient).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledWith({ body: { vpId: "server-vp-1", type: "SET_SELECTION", @@ -1645,7 +1710,7 @@ describe("ServerProxy", () => { TEST_setRequestId(1); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); // prettier-ignore serverProxy.handleMessageFromClient({ @@ -1655,9 +1720,9 @@ describe("ServerProxy", () => { }); expect(postMessageToClient).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ body: { vpId: "server-vp-1", type: "SET_SELECTION", @@ -1673,10 +1738,19 @@ describe("ServerProxy", () => { }); describe("filtering", () => { - it("invokes filter on viewport, which stores current filter criteria", () => { + it("invokes filter on viewport, which stores current filter criteria", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + } + ); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1688,7 +1762,7 @@ describe("ServerProxy", () => { TEST_setRequestId(1); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -1715,10 +1789,19 @@ describe("ServerProxy", () => { }); }); - it("sets batch mode when a filter has been applied", () => { + it("sets batch mode when a filter has been applied", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + } + ); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1730,7 +1813,7 @@ describe("ServerProxy", () => { TEST_setRequestId(1); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -1794,10 +1877,20 @@ describe("ServerProxy", () => { size: 43 }); }); - it("handles TABLE_ROWS that preceed filter request together with filtered rows, in same batch", () => { + + it("handles TABLE_ROWS that preceed filter request together with filtered rows, in same batch", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + } + ); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1809,7 +1902,7 @@ describe("ServerProxy", () => { TEST_setRequestId(1); postMessageToClient.mockClear(); - mockConnection.send.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -1886,16 +1979,19 @@ describe("ServerProxy", () => { }); describe("GroupBy", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription(); + it("sets viewport isTree when groupby in place", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; - it("sets viewport isTree when groupby in place", () => { - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { connection } + ); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1906,8 +2002,8 @@ describe("ServerProxy", () => { }); TEST_setRequestId(1); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -1915,10 +2011,10 @@ describe("ServerProxy", () => { groupBy: ["col-4"], }); - expect(callback).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ body: { aggregations: [], viewPortId: "server-vp-1", @@ -1952,14 +2048,21 @@ describe("ServerProxy", () => { expect(serverProxy["viewports"].get("server-vp-1")?.isTree).toBe(true); }); - it("on changing group, sends grouped records as batch, with SIZE record", () => { - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + it("on changing group, sends grouped records as batch, with SIZE record", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + } + ); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -1970,8 +2073,8 @@ describe("ServerProxy", () => { }); TEST_setRequestId(1); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -1979,9 +2082,9 @@ describe("ServerProxy", () => { groupBy: ["col-4"], }); - expect(callback).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledWith({ body: { aggregations: [], viewPortId: "server-vp-1", @@ -2012,16 +2115,16 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer(createTableGroupRows()); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); expect( serverProxy["viewports"].get("server-vp-1")?.["dataWindow"]?.[ "internalData" ] ).toHaveLength(4); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -2035,14 +2138,21 @@ describe("ServerProxy", () => { }); }); - it("on changing group, sends grouped records as batch, without SIZE record", () => { - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + it("on changing group, sends grouped records as batch, without SIZE record", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + } + ); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -2053,8 +2163,8 @@ describe("ServerProxy", () => { }); TEST_setRequestId(1); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -2062,9 +2172,9 @@ describe("ServerProxy", () => { groupBy: ["col-4"], }); - expect(callback).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledWith({ body: { aggregations: [], viewPortId: "server-vp-1", @@ -2095,16 +2205,16 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer(createTableGroupRows(false)); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); expect( serverProxy["viewports"].get("server-vp-1")?.["dataWindow"]?.[ "internalData" ] ).toHaveLength(4); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -2118,14 +2228,22 @@ describe("ServerProxy", () => { }); }); - it("on changing group, it may receive group records in multiple batches", () => { - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + it("on changing group, it may receive group records in multiple batches", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 10, + connection, + } + ); + + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -2136,8 +2254,8 @@ describe("ServerProxy", () => { }); TEST_setRequestId(1); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -2145,9 +2263,9 @@ describe("ServerProxy", () => { groupBy: ["col-4"], }); - expect(callback).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(1); + expect(connection.send).toHaveBeenCalledWith({ body: { aggregations: [], viewPortId: "server-vp-1", @@ -2178,7 +2296,7 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); + postMessageToClient.mockClear(); const groupRows = createTableGroupRows(false); const group1 = { @@ -2207,7 +2325,7 @@ describe("ServerProxy", () => { serverProxy.handleMessageFromServer(group1); serverProxy.handleMessageFromServer(group2); - expect(callback).toHaveBeenCalledTimes(2); + expect(postMessageToClient).toHaveBeenCalledTimes(2); expect( serverProxy["viewports"].get("server-vp-1")?.["dataWindow"]?.[ "internalData" @@ -2215,7 +2333,7 @@ describe("ServerProxy", () => { ).toHaveLength(4); // prettier-ignore - expect(callback).toHaveBeenNthCalledWith(1, { + expect(postMessageToClient).toHaveBeenNthCalledWith(1, { mode: "size-only", type: "viewport-update", clientViewportId: "client-vp-1", @@ -2223,7 +2341,7 @@ describe("ServerProxy", () => { }) // prettier-ignore - expect(callback).toHaveBeenNthCalledWith(2, { + expect(postMessageToClient).toHaveBeenNthCalledWith(2, { mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -2236,11 +2354,21 @@ describe("ServerProxy", () => { }); }); - it("ignores regular row updates after grouping is in place", () => { - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + it("ignores regular row updates after grouping is in place", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + } + ); + + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -2251,8 +2379,8 @@ describe("ServerProxy", () => { }); TEST_setRequestId(1); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -2274,7 +2402,7 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -2284,14 +2412,25 @@ describe("ServerProxy", () => { }, }); - expect(callback).toHaveBeenCalledTimes(0); + expect(postMessageToClient).toHaveBeenCalledTimes(0); }); - it("processes group row updates", () => { - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + it("processes group row updates", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + bufferSize: 10, + connection, + } + ); + + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -2302,8 +2441,8 @@ describe("ServerProxy", () => { }); TEST_setRequestId(1); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -2325,18 +2464,18 @@ describe("ServerProxy", () => { }, }); - callback.mockClear(); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer(createTableGroupRows()); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); expect( serverProxy["viewports"].get("server-vp-1")?.["dataWindow"]?.[ "internalData" ] ).toHaveLength(4); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "batch", type: "viewport-update", clientViewportId: "client-vp-1", @@ -2349,7 +2488,7 @@ describe("ServerProxy", () => { size: 4, }); - callback.mockClear(); + postMessageToClient.mockClear(); // prettier-ignore serverProxy.handleMessageFromServer({ @@ -2370,14 +2509,14 @@ describe("ServerProxy", () => { }, }); - expect(callback).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(1); expect( serverProxy["viewports"].get("server-vp-1")?.["dataWindow"]?.[ "internalData" ] ).toHaveLength(4); // prettier-ignore - expect(callback).toHaveBeenCalledWith({ + expect(postMessageToClient).toHaveBeenCalledWith({ mode: "update", type: "viewport-update", clientViewportId: "client-vp-1", @@ -2389,17 +2528,11 @@ describe("ServerProxy", () => { }); describe("SIZE records", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription(); - - it("subscribe whilst table is loading", () => { + it("subscribe whilst table is loading", async () => { const postMessageToClient = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - //TODO we shouldn't be able to bypass checks like this - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; - - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); postMessageToClient.mockClear(); @@ -2520,25 +2653,16 @@ describe("ServerProxy", () => { }); }); - describe("on visual linking", () => { - it("returns link table rows", () => { - const [clientSubscription1, serverSubscriptionAck1] = - createSubscription(); - const [clientSubscription2, serverSubscriptionAck2] = createSubscription({ - key: "2", - }); - + describe("on visual linking", async () => { + it("returns link table rows", async () => { const postMessageToClient = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - //TODO we shouldn't be able to bypass checks like this - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + await subscribe(serverProxy, { key: "2" }); - serverProxy.subscribe(clientSubscription2); - serverProxy.handleMessageFromServer(serverSubscriptionAck2); + postMessageToClient.mockClear(); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -2618,10 +2742,11 @@ describe("ServerProxy", () => { }); describe("debounce mode", () => { - it("clears pending range request when request is filled", () => { + it("clears pending range request when request is filled", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); const viewport = serverProxy["viewports"].get("server-vp-1") as Viewport; expect(viewport["pendingRangeRequests"]).toHaveLength(0); @@ -2665,10 +2790,11 @@ describe("ServerProxy", () => { expect(viewport["pendingRangeRequests"]).toHaveLength(0); }); - it("clears pending range request when only partial set of rows received", () => { + it("clears pending range request when only partial set of rows received", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); const viewport = serverProxy["viewports"].get("server-vp-1") as Viewport; expect(viewport["pendingRangeRequests"]).toHaveLength(0); @@ -2708,10 +2834,11 @@ describe("ServerProxy", () => { expect(viewport["pendingRangeRequests"]).toHaveLength(0); }); - it("queues pending range requests, until filled, no message to client until current client range filled", () => { + it("queues pending range requests, until filled, no message to client until current client range filled", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); const viewport = serverProxy["viewports"].get("server-vp-1") as Viewport; expect(viewport["pendingRangeRequests"]).toHaveLength(0); @@ -2792,10 +2919,11 @@ describe("ServerProxy", () => { expect(viewport["pendingRangeRequests"]).toHaveLength(0); }); - it("sends debounce request to client when rows requested before previous request acked", () => { + it("sends debounce request to client when rows requested before previous request acked", async () => { const postMessageToClient = vi.fn(); - const serverProxy = - createServerProxyAndSubscribeToViewport(postMessageToClient); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); // prettier-ignore serverProxy.handleMessageFromServer({ @@ -2836,16 +2964,19 @@ describe("ServerProxy", () => { }); describe("config", () => { - const [clientSubscription1, serverSubscriptionAck1] = createSubscription(); - - it("sets viewport isTree when config includes groupby", () => { - const callback = vi.fn(); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy["sessionId"] = "dsdsd"; - serverProxy["authToken"] = "test"; + it("sets viewport isTree when config includes groupby", async () => { + const postMessageToClient = vi.fn(); + const connection = { + send: vi.fn(), + status: "ready" as const, + }; - serverProxy.subscribe(clientSubscription1); - serverProxy.handleMessageFromServer(serverSubscriptionAck1); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient, + { + connection, + } + ); serverProxy.handleMessageFromServer({ ...COMMON_ATTRS, @@ -2856,8 +2987,8 @@ describe("ServerProxy", () => { }); TEST_setRequestId(1); - callback.mockClear(); - mockConnection.send.mockClear(); + postMessageToClient.mockClear(); + connection.send.mockClear(); serverProxy.handleMessageFromClient({ viewport: "client-vp-1", @@ -2871,10 +3002,10 @@ describe("ServerProxy", () => { }, }); - expect(callback).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledTimes(1); + expect(postMessageToClient).toHaveBeenCalledTimes(0); + expect(connection.send).toHaveBeenCalledTimes(1); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ body: { aggregations: [], viewPortId: "server-vp-1", @@ -2910,75 +3041,57 @@ describe("ServerProxy", () => { }); describe("multiple subscriptions", () => { - it("sends messages to correct clients once subscribed", () => { - const callback = vi.fn(); - const [clientSubscription1, serverSubscription1] = createSubscription({ - key: "1", - }); - const [clientSubscription2, serverSubscription2] = createSubscription({ - key: "2", - }); - const serverProxy = new ServerProxy(mockConnection, callback); - serverProxy.subscribe(clientSubscription1); - serverProxy.subscribe(clientSubscription2); - serverProxy.handleMessageFromServer(serverSubscription1); - serverProxy.handleMessageFromServer(serverSubscription2); - //TODO cover tableSchema in test - expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenNthCalledWith<[DataSourceSubscribedMessage]>( - 1, - { - aggregations: [], - clientViewportId: "client-vp-1", - columns: ["col-1", "col-2", "col-3", "col-4"], - filter: { filter: "" }, - groupBy: [], - range: { - from: 0, - to: 10, - }, - sort: { - sortDefs: [], - }, - tableSchema: null, - type: "subscribed", - } - ); - expect(callback).toHaveBeenNthCalledWith<[DataSourceSubscribedMessage]>( - 2, - { - aggregations: [], - clientViewportId: "client-vp-2", - columns: ["col-1", "col-2", "col-3", "col-4"], - filter: { filter: "" }, - groupBy: [], - range: { - from: 0, - to: 10, - }, - sort: { - sortDefs: [], - }, - tableSchema: null, - type: "subscribed", - } + it("sends messages to correct clients once subscribed", async () => { + const postMessageToClient = vi.fn(); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient ); + + await subscribe(serverProxy, { key: "2" }); + + expect(postMessageToClient).toHaveBeenCalledTimes(2); + expect(postMessageToClient).toHaveBeenNthCalledWith(1, { + aggregations: [], + clientViewportId: "client-vp-1", + columns: ["col-1", "col-2", "col-3", "col-4"], + filter: { filter: "" }, + groupBy: [], + range: { + from: 0, + to: 10, + }, + sort: { + sortDefs: [], + }, + tableSchema: testSchema, + type: "subscribed", + }); + expect(postMessageToClient).toHaveBeenNthCalledWith(2, { + aggregations: [], + clientViewportId: "client-vp-2", + columns: ["col-1", "col-2", "col-3", "col-4"], + filter: { filter: "" }, + groupBy: [], + range: { + from: 0, + to: 10, + }, + sort: { + sortDefs: [], + }, + tableSchema: testSchema, + type: "subscribed", + }); expect(serverProxy["viewports"].size).toEqual(2); }); - it("sends data to each client when initial full datasets are received as separate batches", () => { + it("sends data to each client when initial full datasets are received as separate batches", async () => { const postMessageToClient = vi.fn(); - const [clientSubscription1, serverSubscription1] = createSubscription({ - key: "1", - }); - const [clientSubscription2, serverSubscription2] = createSubscription({ - key: "2", - }); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy.subscribe(clientSubscription1); - serverProxy.subscribe(clientSubscription2); - serverProxy.handleMessageFromServer(serverSubscription1); - serverProxy.handleMessageFromServer(serverSubscription2); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); + + await subscribe(serverProxy, { key: "2" }); postMessageToClient.mockClear(); @@ -3050,19 +3163,13 @@ describe("ServerProxy", () => { ); }); - it("sends data to each client when initial full datasets are received interleaved", () => { + it("sends data to each client when initial full datasets are received interleaved", async () => { const postMessageToClient = vi.fn(); - const [clientSubscription1, serverSubscription1] = createSubscription({ - key: "1", - }); - const [clientSubscription2, serverSubscription2] = createSubscription({ - key: "2", - }); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy.subscribe(clientSubscription1); - serverProxy.subscribe(clientSubscription2); - serverProxy.handleMessageFromServer(serverSubscription1); - serverProxy.handleMessageFromServer(serverSubscription2); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); + + await subscribe(serverProxy, { key: "2" }); postMessageToClient.mockClear(); @@ -3127,19 +3234,13 @@ describe("ServerProxy", () => { ); }); - it("sends data to each client followed by mixed updates", () => { + it("sends data to each client followed by mixed updates", async () => { const postMessageToClient = vi.fn(); - const [clientSubscription1, serverSubscription1] = createSubscription({ - key: "1", - }); - const [clientSubscription2, serverSubscription2] = createSubscription({ - key: "2", - }); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy.subscribe(clientSubscription1); - serverProxy.subscribe(clientSubscription2); - serverProxy.handleMessageFromServer(serverSubscription1); - serverProxy.handleMessageFromServer(serverSubscription2); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); + + await subscribe(serverProxy, { key: "2" }); postMessageToClient.mockClear(); @@ -3205,19 +3306,13 @@ describe("ServerProxy", () => { }); }); - it("sends mixed updates, including size change for one vp", () => { + it("sends mixed updates, including size change for one vp", async () => { const postMessageToClient = vi.fn(); - const [clientSubscription1, serverSubscription1] = createSubscription({ - key: "1", - }); - const [clientSubscription2, serverSubscription2] = createSubscription({ - key: "2", - }); - const serverProxy = new ServerProxy(mockConnection, postMessageToClient); - serverProxy.subscribe(clientSubscription1); - serverProxy.subscribe(clientSubscription2); - serverProxy.handleMessageFromServer(serverSubscription1); - serverProxy.handleMessageFromServer(serverSubscription2); + const serverProxy = await createServerProxyAndSubscribeToViewport( + postMessageToClient + ); + + await subscribe(serverProxy, { key: "2" }); postMessageToClient.mockClear(); @@ -3330,12 +3425,17 @@ describe("ServerProxy", () => { }); describe("disable and enable", () => { - it("sends a message to server when client calls disable", () => { + it("sends a message to server when client calls disable", async () => { const postMessageToClient = vi.fn(); - const serverProxy = createServerProxyAndSubscribeToViewport( + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, { - connection: mockConnection, + connection, } ); // prettier-ignore @@ -3347,7 +3447,7 @@ describe("ServerProxy", () => { }, }); - mockConnection.send.mockClear(); + connection.send.mockClear(); postMessageToClient.mockClear(); expect(serverProxy["viewports"].get("server-vp-1")?.disabled).toBe(false); @@ -3358,10 +3458,10 @@ describe("ServerProxy", () => { type: "disable", viewport: "client-vp-1", }); - expect(mockConnection.send).toBeCalledTimes(1); + expect(connection.send).toBeCalledTimes(1); expect(postMessageToClient).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ body: { type: "DISABLE_VP", viewPortId: "server-vp-1", @@ -3384,12 +3484,16 @@ describe("ServerProxy", () => { expect(serverProxy["viewports"].get("server-vp-1")?.disabled).toBe(true); }); - it("sends a message to server when client calls enable, re-sends data from cache to client", () => { + it("sends a message to server when client calls enable, re-sends data from cache to client", async () => { const postMessageToClient = vi.fn(); - const serverProxy = createServerProxyAndSubscribeToViewport( + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, { - connection: mockConnection, + connection, } ); // prettier-ignore @@ -3417,7 +3521,7 @@ describe("ServerProxy", () => { }, }); - mockConnection.send.mockClear(); + connection.send.mockClear(); postMessageToClient.mockClear(); TEST_setRequestId(1); @@ -3428,10 +3532,10 @@ describe("ServerProxy", () => { type: "enable", viewport: "client-vp-1", }); - expect(mockConnection.send).toBeCalledTimes(1); + expect(connection.send).toBeCalledTimes(1); expect(postMessageToClient).toHaveBeenCalledTimes(0); - expect(mockConnection.send).toHaveBeenCalledWith({ + expect(connection.send).toHaveBeenCalledWith({ body: { type: "ENABLE_VP", viewPortId: "server-vp-1", @@ -3483,12 +3587,17 @@ describe("ServerProxy", () => { }); }); - it("does nothing if client calls enable for a viewport which has not been disabled", () => { + it("does nothing if client calls enable for a viewport which has not been disabled", async () => { const postMessageToClient = vi.fn(); - const serverProxy = createServerProxyAndSubscribeToViewport( + const connection = { + send: vi.fn(), + status: "ready" as const, + }; + + const serverProxy = await createServerProxyAndSubscribeToViewport( postMessageToClient, { - connection: mockConnection, + connection, } ); // prettier-ignore @@ -3500,7 +3609,7 @@ describe("ServerProxy", () => { }, }); - mockConnection.send.mockClear(); + connection.send.mockClear(); postMessageToClient.mockClear(); TEST_setRequestId(1); @@ -3509,7 +3618,7 @@ describe("ServerProxy", () => { type: "enable", viewport: "client-vp-1", }); - expect(mockConnection.send).toBeCalledTimes(0); + expect(connection.send).toBeCalledTimes(0); expect(postMessageToClient).toHaveBeenCalledTimes(0); }); }); diff --git a/vuu-ui/packages/vuu-data/test/test-utils.ts b/vuu-ui/packages/vuu-data/test/test-utils.ts index 43a4d2e0c..14abfc90c 100644 --- a/vuu-ui/packages/vuu-data/test/test-utils.ts +++ b/vuu-ui/packages/vuu-data/test/test-utils.ts @@ -2,6 +2,7 @@ import { vi } from "vitest"; import { ServerToClientCreateViewPortSuccess, ServerToClientMessage, + ServerToClientTableMeta, ServerToClientTableRows, VuuRow, } from "@finos/vuu-protocol-types"; @@ -156,6 +157,17 @@ export const updateTableRow = ( }; }; +export const testSchema = { + columns: [ + { name: "col-1", serverDataType: "string" }, + { name: "col-2", serverDataType: "string" }, + { name: "col-3", serverDataType: "string" }, + { name: "col-4", serverDataType: "string" }, + ], + key: "col-1", + table: { module: "TEST", table: "test-table" }, +}; + // prettier-ignore export const createSubscription = ({ aggregations = [], @@ -170,7 +182,8 @@ export const createSubscription = ({ viewport = `client-vp-${key}` } = {}): [ ServerProxySubscribeMessage, - ServerToClientMessage + ServerToClientMessage, + ServerToClientMessage ] => [ { aggregations, @@ -199,6 +212,19 @@ export const createSubscription = ({ }, token: "", user: "user" + }, { + module: "TEST", + requestId: `1`, + body: { + columns: ['col-1', 'col-2', 'col-3', 'col-4'], + key: 'col-1', + dataTypes: ['string','string','string','string'], + table: {module: "TEST", table: "test-table"}, + type: "TABLE_META_RESP" + }, + token: "", + user: "user" + } ]; @@ -207,26 +233,46 @@ const mockConnection = { status: "ready" as const, }; -export const createServerProxyAndSubscribeToViewport = ( +export const subscribe = async ( + serverProxy: ServerProxy, + { bufferSize = 0, key = "1", to = 10 }: SubscriptionDetails +) => { + const [clientSubscription, serverSubscriptionAck, tableMetaResponse] = + createSubscription({ + bufferSize, + key, + to, + }); + + serverProxy.subscribe(clientSubscription); + serverProxy.handleMessageFromServer(serverSubscriptionAck); + serverProxy.handleMessageFromServer(tableMetaResponse); + + // allow the promises pending for the subscription and ,etadata to resolve + await new Promise((resolve) => window.setTimeout(resolve, 0)); +}; + +export type SubscriptionDetails = { + bufferSize?: number; + key?: string; + to?: number; +}; + +export const createServerProxyAndSubscribeToViewport = async ( postMessageToClient: any, { bufferSize = 0, connection = mockConnection, - }: { bufferSize?: number; connection?: any } = {} + key = "1", + to = 10, + }: { bufferSize?: number; connection?: any; key?: string; to?: number } = {} ) => { const serverProxy = new ServerProxy(connection, postMessageToClient); //TODO we shouldn't be able to bypass checks like this serverProxy["sessionId"] = "dsdsd"; serverProxy["authToken"] = "test"; - const [clientSubscription, serverSubscriptionAck] = createSubscription({ - bufferSize, - }); - - serverProxy.subscribe(clientSubscription); - serverProxy.handleMessageFromServer(serverSubscriptionAck); - - postMessageToClient.mockClear(); + await subscribe(serverProxy, { bufferSize, key, to }); return serverProxy; }; diff --git a/vuu-ui/packages/vuu-data/test/viewport.test.ts b/vuu-ui/packages/vuu-data/test/viewport.test.ts index 0607ef858..2e7b54be8 100644 --- a/vuu-ui/packages/vuu-data/test/viewport.test.ts +++ b/vuu-ui/packages/vuu-data/test/viewport.test.ts @@ -3,8 +3,12 @@ import { ServerToClientCreateViewPortSuccess } from "@finos/vuu-protocol-types"; import { describe, expect, it } from "vitest"; import { ServerProxySubscribeMessage } from "../src"; import { Viewport } from "../src/server-proxy/viewport"; -import { createSubscription, createTableRows, sizeRow } from "./test-utils"; -import { TableSchema } from "../src/message-utils"; +import { + createSubscription, + createTableRows, + sizeRow, + testSchema, +} from "./test-utils"; const config_options = { aggregations: [], @@ -22,6 +26,8 @@ const vuu_config_options = { sort: { sortDefs: [] }, }; +const noop = () => undefined; + const vuu_table = { module: "TEST", table: "test-table" }; const constructor_options = { @@ -34,14 +40,14 @@ const constructor_options = { describe("Viewport", () => { describe("constructor", () => { it("initial status is empty", () => { - const vp = new Viewport(constructor_options); + const vp = new Viewport(constructor_options, noop); expect(vp.status).toEqual(""); }); }); describe("subscribe", () => { it("uses constructor params to construct subscribe message", () => { - const vp = new Viewport(constructor_options); + const vp = new Viewport(constructor_options, noop); const message = vp.subscribe(); const { filter: { filter }, @@ -59,16 +65,19 @@ describe("Viewport", () => { }); }); it("sets status to subscribing", () => { - const vp = new Viewport(constructor_options); + const vp = new Viewport(constructor_options, noop); vp.subscribe(); expect(vp.status).toEqual("subscribing"); }); it("uses bufferSize when constructing range", () => { - const vp = new Viewport({ - bufferSize: 100, - ...constructor_options, - }); + const vp = new Viewport( + { + bufferSize: 100, + ...constructor_options, + }, + noop + ); const message = vp.subscribe(); const { filter: { filter }, @@ -86,11 +95,14 @@ describe("Viewport", () => { }); }); it("applies bufferSize to existing range", () => { - const vp = new Viewport({ - ...constructor_options, - bufferSize: 100, - range: { from: 0, to: 100 }, - }); + const vp = new Viewport( + { + ...constructor_options, + bufferSize: 100, + range: { from: 0, to: 100 }, + }, + noop + ); const message = vp.subscribe(); const { filter: { filter }, @@ -107,11 +119,14 @@ describe("Viewport", () => { }); }); it("splits bufferSize around existing range", () => { - const vp = new Viewport({ - ...constructor_options, - bufferSize: 100, - range: { from: 100, to: 200 }, - }); + const vp = new Viewport( + { + ...constructor_options, + bufferSize: 100, + range: { from: 100, to: 200 }, + }, + noop + ); const message = vp.subscribe(); const { filter: { filter }, @@ -131,7 +146,7 @@ describe("Viewport", () => { describe("subscribed", () => { it("sets status to subscribed", () => { - const vp = new Viewport(constructor_options); + const vp = new Viewport(constructor_options, noop); const vuuMessageBody: ServerToClientCreateViewPortSuccess = { ...vuu_config_options, range: { from: 0, to: 50 }, @@ -144,7 +159,7 @@ describe("Viewport", () => { }); it("echos back subscription details, enriching values sent by server", () => { - const vp = new Viewport(constructor_options); + const vp = new Viewport(constructor_options, noop); const vuuMessageBody: ServerToClientCreateViewPortSuccess = { ...vuu_config_options, range: { from: 0, to: 50 }, @@ -152,41 +167,13 @@ describe("Viewport", () => { table: vuu_table.table, viewPortId: "server-vp1", }; - const message = vp.handleSubscribed(vuuMessageBody); + const message = vp.handleSubscribed(vuuMessageBody, testSchema); expect(message).toEqual({ ...config_options, clientViewportId: constructor_options.viewport, range: { from: 0, to: 50 }, - tableSchema: null, - type: "subscribed", - }); - }); - it("includes tableSchema, when this has been received", () => { - const vp = new Viewport(constructor_options); - const vuuMessageBody: ServerToClientCreateViewPortSuccess = { - ...vuu_config_options, - range: { from: 0, to: 50 }, - type: "CREATE_VP_SUCCESS", - table: vuu_table.table, - viewPortId: "server-vp1", - }; - - const tableSchema: TableSchema = { - table: { module: "TEST", table: "testTable" }, - key: "col1", - columns: [{ name: "col1", serverDataType: "string" }], - }; - - vp.setTableSchema(tableSchema); - - const message = vp.handleSubscribed(vuuMessageBody); - - expect(message).toEqual({ - ...config_options, - clientViewportId: constructor_options.viewport, - range: { from: 0, to: 50 }, - tableSchema, + tableSchema: testSchema, type: "subscribed", }); }); @@ -194,11 +181,14 @@ describe("Viewport", () => { describe("pending range requests", () => { it("holds requests in pending queue, marking them when acked, until first rows received", () => { - const vp = new Viewport({ - ...constructor_options, - bufferSize: 10, - range: { from: 0, to: 10 }, - }); + const vp = new Viewport( + { + ...constructor_options, + bufferSize: 10, + range: { from: 0, to: 10 }, + }, + noop + ); const [, serverSubscription] = createSubscription(); vp.handleSubscribed(serverSubscription.body); @@ -223,11 +213,14 @@ describe("Viewport", () => { describe("rangeRequestAlreadyPending", () => { it("tests range requests against last pending server request", () => { - const vp = new Viewport({ - ...constructor_options, - bufferSize: 10, - range: { from: 0, to: 10 }, - }); + const vp = new Viewport( + { + ...constructor_options, + bufferSize: 10, + range: { from: 0, to: 10 }, + }, + noop + ); const [, serverSubscription] = createSubscription(); vp.handleSubscribed(serverSubscription.body); @@ -258,11 +251,14 @@ describe("Viewport", () => { }); it("tests range requests against multiple pending server requests", () => { - const vp = new Viewport({ - ...constructor_options, - bufferSize: 10, - range: { from: 0, to: 10 }, - }); + const vp = new Viewport( + { + ...constructor_options, + bufferSize: 10, + range: { from: 0, to: 10 }, + }, + noop + ); const [, serverSubscription] = createSubscription(); vp.handleSubscribed(serverSubscription.body); @@ -299,11 +295,14 @@ describe("Viewport", () => { describe("groupBy", () => { it("clears dataWindow when a groupBy request is received on a non grouped viewport", () => { - const vp = new Viewport({ - ...constructor_options, - bufferSize: 10, - range: { from: 0, to: 10 }, - }); + const vp = new Viewport( + { + ...constructor_options, + bufferSize: 10, + range: { from: 0, to: 10 }, + }, + noop + ); const [, serverSubscription] = createSubscription(); vp.handleSubscribed(serverSubscription.body);