widget.spec.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import expect = require('expect.js');
  4. import {
  5. ClientSession
  6. } from '@jupyterlab/apputils';
  7. import {
  8. Kernel
  9. } from '@jupyterlab/services';
  10. import {
  11. Message
  12. } from '@phosphor/messaging';
  13. import {
  14. Widget
  15. } from '@phosphor/widgets';
  16. import {
  17. IOutputAreaModel, OutputAreaModel, OutputArea
  18. } from '@jupyterlab/outputarea';
  19. import {
  20. createClientSession, defaultRenderMime, DEFAULT_OUTPUTS
  21. } from '../../utils';
  22. /**
  23. * The default rendermime instance to use for testing.
  24. */
  25. const rendermime = defaultRenderMime();
  26. const CODE = 'print("hello")';
  27. class LogOutputArea extends OutputArea {
  28. methods: string[] = [];
  29. protected onUpdateRequest(msg: Message): void {
  30. super.onUpdateRequest(msg);
  31. this.methods.push('onUpdateRequest');
  32. }
  33. protected onModelChanged(sender: IOutputAreaModel, args: IOutputAreaModel.ChangedArgs) {
  34. super.onModelChanged(sender, args);
  35. this.methods.push('onModelChanged');
  36. }
  37. }
  38. describe('outputarea/widget', () => {
  39. let widget: LogOutputArea;
  40. let model: OutputAreaModel;
  41. beforeEach(() => {
  42. model = new OutputAreaModel({ values: DEFAULT_OUTPUTS, trusted: true });
  43. widget = new LogOutputArea({ rendermime, model });
  44. });
  45. afterEach(() => {
  46. model.dispose();
  47. widget.dispose();
  48. });
  49. describe('OutputArea', () => {
  50. describe('#constructor()', () => {
  51. it('should create an output area widget', () => {
  52. expect(widget).to.be.an(OutputArea);
  53. expect(widget.hasClass('jp-OutputArea')).to.be(true);
  54. });
  55. it('should take an optional contentFactory', () => {
  56. let contentFactory = Object.create(OutputArea.defaultContentFactory);
  57. let widget = new OutputArea({ rendermime, contentFactory, model });
  58. expect(widget.contentFactory).to.be(contentFactory);
  59. });
  60. });
  61. describe('#model', () => {
  62. it('should be the model used by the widget', () => {
  63. expect(widget.model).to.be(model);
  64. });
  65. });
  66. describe('#rendermime', () => {
  67. it('should be the rendermime instance used by the widget', () => {
  68. expect(widget.rendermime).to.be(rendermime);
  69. });
  70. });
  71. describe('#contentFactory', () => {
  72. it('should be the contentFactory used by the widget', () => {
  73. expect(widget.contentFactory).to.be(OutputArea.defaultContentFactory);
  74. });
  75. });
  76. describe('#widgets', () => {
  77. it('should get the child widget at the specified index', () => {
  78. expect(widget.widgets[0]).to.be.a(Widget);
  79. });
  80. it('should get the number of child widgets', () => {
  81. expect(widget.widgets.length).to.be(DEFAULT_OUTPUTS.length - 1);
  82. widget.model.clear();
  83. expect(widget.widgets.length).to.be(0);
  84. });
  85. });
  86. describe('#future', () => {
  87. let session: ClientSession;
  88. beforeEach(() => {
  89. return createClientSession().then(s => {
  90. session = s;
  91. return session.initialize();
  92. });
  93. });
  94. afterEach(() => {
  95. return session.shutdown().then(() => {
  96. session.dispose();
  97. });
  98. });
  99. it('should execute code on a kernel and send outputs to the model', () => {
  100. return session.kernel.ready.then(() => {
  101. let future = session.kernel.requestExecute({ code: CODE });
  102. widget.future = future;
  103. return future.done;
  104. }).then(reply => {
  105. expect(reply.content.execution_count).to.be.ok();
  106. expect(reply.content.status).to.be('ok');
  107. expect(model.length).to.be(1);
  108. });
  109. });
  110. it('should clear existing outputs', () => {
  111. widget.model.fromJSON(DEFAULT_OUTPUTS);
  112. return session.kernel.ready.then(() => {
  113. let future = session.kernel.requestExecute({ code: CODE });
  114. widget.future = future;
  115. return future.done;
  116. }).then(reply => {
  117. expect(reply.content.execution_count).to.be.ok();
  118. expect(model.length).to.be(1);
  119. });
  120. });
  121. });
  122. describe('#onModelChanged()', () => {
  123. it('should handle an added output', () => {
  124. widget.model.clear();
  125. widget.methods = [];
  126. widget.model.add(DEFAULT_OUTPUTS[0]);
  127. expect(widget.methods).to.contain('onModelChanged');
  128. expect(widget.widgets.length).to.be(1);
  129. });
  130. it('should handle a clear', () => {
  131. widget.model.fromJSON(DEFAULT_OUTPUTS);
  132. widget.methods = [];
  133. widget.model.clear();
  134. expect(widget.methods).to.contain('onModelChanged');
  135. expect(widget.widgets.length).to.be(0);
  136. });
  137. it('should handle a set', () => {
  138. widget.model.clear();
  139. widget.model.add(DEFAULT_OUTPUTS[0]);
  140. widget.methods = [];
  141. widget.model.add(DEFAULT_OUTPUTS[0]);
  142. expect(widget.methods).to.contain('onModelChanged');
  143. expect(widget.widgets.length).to.be(1);
  144. });
  145. });
  146. describe('.execute()', () => {
  147. let session: ClientSession;
  148. beforeEach(() => {
  149. return createClientSession().then(s => {
  150. session = s;
  151. return session.initialize();
  152. });
  153. });
  154. afterEach(() => {
  155. return session.shutdown().then(() => {
  156. session.dispose();
  157. });
  158. });
  159. it('should execute code on a kernel and send outputs to the model', () => {
  160. return session.kernel.ready.then(() => {
  161. return OutputArea.execute(CODE, widget, session).then(reply => {
  162. expect(reply.content.execution_count).to.be.ok();
  163. expect(reply.content.status).to.be('ok');
  164. expect(model.length).to.be(1);
  165. });
  166. });
  167. });
  168. it('should clear existing outputs', () => {
  169. widget.model.fromJSON(DEFAULT_OUTPUTS);
  170. return OutputArea.execute(CODE, widget, session).then(reply => {
  171. expect(reply.content.execution_count).to.be.ok();
  172. expect(model.length).to.be(1);
  173. });
  174. });
  175. it('should handle routing of display messages', () => {
  176. let model0 = new OutputAreaModel({ trusted: true });
  177. let widget0 = new LogOutputArea({ rendermime, model: model0 });
  178. let model1 = new OutputAreaModel({ trusted: true });
  179. let widget1 = new LogOutputArea({ rendermime, model: model1 });
  180. let model2 = new OutputAreaModel({ trusted: true });
  181. let widget2 = new LogOutputArea({ rendermime, model: model2 });
  182. let code0 = [
  183. 'ip = get_ipython()',
  184. 'from IPython.display import display',
  185. 'def display_with_id(obj, display_id, update=False):',
  186. ' iopub = ip.kernel.iopub_socket',
  187. ' session = get_ipython().kernel.session',
  188. ' data, md = ip.display_formatter.format(obj)',
  189. ' transient = {"display_id": display_id}',
  190. ' content = {"data": data, "metadata": md, "transient": transient}',
  191. ' msg_type = "update_display_data" if update else "display_data"',
  192. ' session.send(iopub, msg_type, content, parent=ip.parent_header)',
  193. ].join('\n');
  194. let code1 = [
  195. 'display("above")',
  196. 'display_with_id(1, "here")',
  197. 'display("below")',
  198. ].join('\n');
  199. let code2 = [
  200. 'display_with_id(2, "here")',
  201. 'display_with_id(3, "there")',
  202. 'display_with_id(4, "here")',
  203. ].join('\n');
  204. let ipySession: ClientSession;
  205. return createClientSession({ kernelPreference: { name: 'ipython' } }).then(s => {
  206. ipySession = s;
  207. return s.initialize();
  208. }).then(() => {
  209. let promise0 = OutputArea.execute(code0, widget0, ipySession);
  210. let promise1 = OutputArea.execute(code1, widget1, ipySession);
  211. return Promise.all([promise0, promise1]);
  212. }).then(() => {
  213. expect(model1.length).to.be(3);
  214. expect(model1.toJSON()[1].data).to.eql({ 'text/plain': '1' });
  215. return OutputArea.execute(code2, widget2, ipySession);
  216. }).then(() => {
  217. expect(model1.length).to.be(3);
  218. expect(model1.toJSON()[1].data).to.eql({ 'text/plain': '4' });
  219. expect(model2.length).to.be(3);
  220. let outputs = model2.toJSON();
  221. expect(outputs[0].data).to.eql({ 'text/plain': '4' });
  222. expect(outputs[1].data).to.eql({ 'text/plain': '3' });
  223. expect(outputs[2].data).to.eql({ 'text/plain': '4' });
  224. return ipySession.shutdown();
  225. });
  226. });
  227. });
  228. describe('.ContentFactory', () => {
  229. describe('#createOutputPrompt()', () => {
  230. it('should create an output prompt', () => {
  231. let factory = new OutputArea.ContentFactory();
  232. expect(factory.createOutputPrompt().executionCount).to.be(null);
  233. });
  234. });
  235. describe('#createStdin()', () => {
  236. it('should create a stdin widget', () => {
  237. return Kernel.startNew().then(kernel => {
  238. let factory = new OutputArea.ContentFactory();
  239. let future = kernel.requestExecute({ code: CODE });
  240. let options = {
  241. prompt: 'hello',
  242. password: false,
  243. future
  244. };
  245. expect(factory.createStdin(options)).to.be.a(Widget);
  246. return kernel.shutdown().then(() => { kernel.dispose(); });
  247. });
  248. });
  249. });
  250. });
  251. describe('.defaultContentFactory', () => {
  252. it('should be a `contentFactory` instance', () => {
  253. expect(OutputArea.defaultContentFactory).to.be.an(OutputArea.ContentFactory);
  254. });
  255. });
  256. });
  257. });