UNPKG

@nteract/messaging

Version:

Messaging mechanics for nteract apps (jupyter spec)

386 lines (360 loc) 10.9 kB
import cloneDeep from "lodash.clonedeep"; import { from, of } from "rxjs"; import { count, map, tap, toArray } from "rxjs/operators"; import { childOf, convertOutputMessageToNotebookFormat, executeRequest, createMessage, createCommMessage, createCommOpenMessage, createCommCloseMessage, executionCounts, JupyterMessage, kernelStatuses, ofMessageType, outputs, payloads } from "../src"; import { displayData, executeInput, executeReply, message, status } from "../src/messages"; import { KernelStatus } from "@nteract/types"; describe("createMessage", () => { it("makes a msg", () => { const msg = createMessage("execute_request", { parent_header: { msg_id: "100" }, content: { data: { foo: "bar" } } }); expect(typeof msg).toBe("object"); expect(typeof msg.header).toBe("object"); expect(typeof msg.content).toBe("object"); expect(msg.header.msg_type).toBe("execute_request"); expect(msg.parent_header.msg_id).toBe("100"); expect(msg.content.data.foo).toBe("bar"); }); }); describe("executeRequest", () => { it("creates an execute_request message", () => { const code = 'print("test")'; const executeReq = executeRequest(code); expect(executeReq.content.code).toEqual(code); expect(executeReq.header.msg_type).toEqual("execute_request"); }); }); describe("createCommMessage", () => { test("creates a comm_msg", () => { const commMessage = createCommMessage("0000", { hey: "is for horses" }); expect(commMessage.content.data).toEqual({ hey: "is for horses" }); expect(commMessage.content.comm_id).toBe("0000"); expect(commMessage.header.msg_type).toBe("comm_msg"); }); }); describe("createCommOpenMessage", () => { test("creates a comm_open", () => { const commMessage = createCommOpenMessage( "0001", "myTarget", { hey: "is for horses" }, "targetModule" ); expect(commMessage.content).toEqual({ comm_id: "0001", target_name: "myTarget", data: { hey: "is for horses" }, target_module: "targetModule" }); }); test("can specify a target_module", () => { const commMessage = createCommOpenMessage( "0001", "myTarget", { hey: "is for horses" }, "Dr. Pepper" ); expect(commMessage.content).toEqual({ comm_id: "0001", target_name: "myTarget", data: { hey: "is for horses" }, target_module: "Dr. Pepper" }); }); }); describe("createCommCloseMessage", () => { test("creates a comm_msg", () => { const parent_header = { id: "23" }; const commMessage = createCommCloseMessage(parent_header, "0000", { hey: "is for horses" }); expect(commMessage.content.data).toEqual({ hey: "is for horses" }); expect(commMessage.content.comm_id).toBe("0000"); expect(commMessage.header.msg_type).toBe("comm_close"); expect(commMessage.parent_header).toEqual(parent_header); }); }); describe("childOf", () => { it("filters messages that have the same parent", () => from([ { parent_header: { msg_id: "100" } }, { parent_header: { msg_id: "100" } }, { parent_header: { msg_id: "200" } }, { parent_header: { msg_id: "300" } }, { parent_header: { msg_id: "100" } } ] as JupyterMessage[]) .pipe(childOf({ header: { msg_id: "100" } } as JupyterMessage), count()) .toPromise() .then(val => { expect(val).toEqual(3); })); // They now get logged instead if bad messages, instead of bombing the stream it.skip("throws an error if msg_id is not present", done => from(([ { parent_header: { msg_id_bad: "100" } }, { parent_header: { msg_id_test: "100" } }, { parent_header: { msg_id_invalid: "200" } }, { parent_header: { msg_id_invalid: "300" } } ] as any[]) as JupyterMessage[]) .pipe(childOf({ header: { msg_id: "100" } } as JupyterMessage)) .subscribe( () => { throw new Error("Subscription was unexpectedly fulfilled."); }, error => { expect(error).not.toBe(null); done(); } )); }); describe("ofMessageType", () => { it("filters messages of type requested", () => { from(([ { header: { msg_type: "stream" } }, { header: { msg_type: "error" } }, { header: { msg_type: "status" } }, { header: { msg_type: "stream" } }, { header: { msg_type: "status" } } ] as any[]) as JupyterMessage[]) .pipe( ofMessageType(["stream", "status"]), tap(val => { expect( val.header.msg_type === "stream" || val.header.msg_type === "error" ); }), map(entry => entry.header.msg_type), count() ) .toPromise() .then(val => { expect(val).toEqual(4); }); }); it("throws an error in msg_type is not present", done => from(([ { header: { msg_type_invalid: "stream" } }, { header: { msg_type_invalid: "status" } }, { header: {} }, { header: { msg_type: "stream" } } ] as any[]) as JupyterMessage[]) .pipe(ofMessageType(["stream", "status"])) .subscribe( () => { throw new Error("Subscription was unexpectedly fulfilled."); }, error => { expect(error).not.toBe(null); done(); } )); it("handles both the legacy and current arguments for ofMessageType", () => { from(([ { header: { msg_type: "stream" } }, { header: { msg_type: "error" } }, { header: { msg_type: "status" } }, { header: { msg_type: "stream" } }, { header: { msg_type: "status" } } ] as any[]) as JupyterMessage[]) .pipe( ofMessageType(["stream", "status"]), tap(val => { expect( val.header.msg_type === "stream" || val.header.msg_type === "status" ); }), map(entry => entry.header.msg_type), count() ) .toPromise() .then(val => { expect(val).toEqual(4); }); from(([ { header: { msg_type: "stream" } }, { header: { msg_type: "status" } }, { header: { msg_type: "error" } }, { header: { msg_type: "stream" } }, { header: { msg_type: "status" } } ] as any[]) as JupyterMessage[]) .pipe( // Note the lack of array brackets on the arguments ofMessageType("stream", "status"), tap(val => { expect( val.header.msg_type === "stream" || val.header.msg_type === "status" ); }), map(entry => entry.header.msg_type), count() ) .toPromise() .then(val => { expect(val).toEqual(4); }); }); }); describe("convertOutputMessageToNotebookFormat", () => { it("ensures that fields end up notebook format style", () => { const message = ({ content: { yep: true }, header: { msg_type: "test", msg_id: "10", username: "rebecca" }, metadata: { purple: true } } as any) as JupyterMessage; expect(convertOutputMessageToNotebookFormat(message)).toEqual({ yep: true, output_type: "test" }); }); it("should not mutate the message", () => { const message = ({ content: { yep: true }, header: { msg_type: "test", msg_id: "10", username: "rebecca" }, metadata: { purple: true } } as any) as JupyterMessage; const copy = cloneDeep(message); convertOutputMessageToNotebookFormat(message); expect(message).toEqual(copy); }); }); describe("outputs", () => { it("extracts outputs as nbformattable contents", () => { const hacking = of( status(KernelStatus.Busy), displayData({ data: { "text/plain": "woo" } }), displayData({ data: { "text/plain": "hoo" } }), status(KernelStatus.Idle) ); return hacking .pipe(outputs(), toArray()) .toPromise() .then(arr => { expect(arr).toEqual([ { data: { "text/plain": "woo" }, output_type: "display_data", metadata: {}, transient: {} }, { data: { "text/plain": "hoo" }, output_type: "display_data", metadata: {}, transient: {} } ]); }); }); }); describe("payloads", () => { it("extracts payloads from execute_reply messages", () => { return of( status(KernelStatus.Idle), status(KernelStatus.Busy), executeReply({ payload: [{ c: "d" }] }), executeReply({ payload: [{ a: "b" }, { g: "6" }] }), executeReply({ status: "ok" }), message( { msg_type: "fake" as any }, { payload: [{ should: "not be in it" }] } ) ) .pipe(payloads(), toArray()) .toPromise() .then(arr => { expect(arr).toEqual([{ c: "d" }, { a: "b" }, { g: "6" }]); }); expect(payloads()).toBeTruthy(); }); }); describe("executionCounts", () => { it("extracts all execution counts from a session", () => { return of( status(KernelStatus.Starting), status(KernelStatus.Idle), status(KernelStatus.Busy), executeInput({ code: "display('woo')\ndisplay('hoo')", execution_count: 0 }), displayData({ data: { "text/plain": "woo" } }), displayData({ data: { "text/plain": "hoo" } }), executeInput({ code: "", execution_count: 1 }), status(KernelStatus.Idle) ) .pipe(executionCounts(), toArray()) .toPromise() .then(arr => { expect(arr).toEqual([0, 1]); }); }); it("extracts all execution counts from a session", () => { return of( status(KernelStatus.Starting), status(KernelStatus.Idle), status(KernelStatus.Busy), executeReply({ status: KernelStatus.Idle, execution_count: 0 }), displayData({ data: { "text/plain": "woo" } }), displayData({ data: { "text/plain": "hoo" } }), executeReply({ status: KernelStatus.Idle, execution_count: 1 }), status(KernelStatus.Idle) ) .pipe(executionCounts(), toArray()) .toPromise() .then(arr => { expect(arr).toEqual([0, 1]); }); }); }); describe("kernelStatuses", () => { it("extracts all the execution states from status messages", () => { return of( status(KernelStatus.Starting), status(KernelStatus.Idle), status(KernelStatus.Busy), displayData({ data: { "text/plain": "woo" } }), displayData({ data: { "text/plain": "hoo" } }), status(KernelStatus.Idle) ) .pipe(kernelStatuses(), toArray()) .toPromise() .then(arr => { expect(arr).toEqual([KernelStatus.Starting, KernelStatus.Idle, KernelStatus.Busy, KernelStatus.Idle]); }); }); });