mock.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. // We explicitly reference the jest typings since the jest.d.ts file shipped
  4. // with jest 26 masks the @types/jest typings
  5. /// <reference types="jest" />
  6. import { ISessionContext, SessionContext } from '@jupyterlab/apputils';
  7. import { Context, TextModelFactory } from '@jupyterlab/docregistry';
  8. import {
  9. Kernel,
  10. KernelMessage,
  11. KernelSpec,
  12. Session,
  13. ServiceManager,
  14. Contents,
  15. ServerConnection,
  16. ContentsManager
  17. } from '@jupyterlab/services';
  18. import { ArrayIterator } from '@lumino/algorithm';
  19. import { AttachedProperty } from '@lumino/properties';
  20. import { UUID } from '@lumino/coreutils';
  21. import { Signal } from '@lumino/signaling';
  22. import { PathExt } from '@jupyterlab/coreutils';
  23. // The default kernel name
  24. export const DEFAULT_NAME = 'python3';
  25. export const KERNELSPECS: { [key: string]: KernelSpec.ISpecModel } = {
  26. [DEFAULT_NAME]: {
  27. argv: [
  28. '/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
  29. '-m',
  30. 'ipykernel_launcher',
  31. '-f',
  32. '{connection_file}'
  33. ],
  34. display_name: 'Python 3',
  35. language: 'python',
  36. metadata: {},
  37. name: DEFAULT_NAME,
  38. resources: {}
  39. },
  40. irkernel: {
  41. argv: [
  42. '/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
  43. '-m',
  44. 'ipykernel_launcher',
  45. '-f',
  46. '{connection_file}'
  47. ],
  48. display_name: 'R',
  49. language: 'python',
  50. metadata: {},
  51. name: 'irkernel',
  52. resources: {}
  53. }
  54. };
  55. export const KERNEL_MODELS: Kernel.IModel[] = [
  56. {
  57. name: DEFAULT_NAME,
  58. id: UUID.uuid4()
  59. },
  60. {
  61. name: 'r',
  62. id: UUID.uuid4()
  63. },
  64. {
  65. name: DEFAULT_NAME,
  66. id: UUID.uuid4()
  67. }
  68. ];
  69. // Notebook Paths for certain kernel name
  70. export const NOTEBOOK_PATHS: { [kernelName: string]: string[] } = {
  71. python3: ['Untitled.ipynb', 'Untitled1.ipynb', 'Untitled2.ipynb'],
  72. r: ['Visualization.ipynb', 'Analysis.ipynb', 'Conclusion.ipynb']
  73. };
  74. /**
  75. * Forceably change the status of a session context.
  76. * An iopub message is emitted for the change.
  77. *
  78. * @param sessionContext The session context of interest.
  79. * @param newStatus The new kernel status.
  80. */
  81. export function updateKernelStatus(
  82. sessionContext: ISessionContext,
  83. newStatus: KernelMessage.Status
  84. ) {
  85. const kernel = sessionContext.session!.kernel!;
  86. (kernel as any).status = newStatus;
  87. (sessionContext.statusChanged as any).emit(newStatus);
  88. const msg = KernelMessage.createMessage({
  89. session: kernel.clientId,
  90. channel: 'iopub',
  91. msgType: 'status',
  92. content: { execution_state: newStatus }
  93. });
  94. emitIopubMessage(sessionContext, msg);
  95. }
  96. /**
  97. * Emit an iopub message on a session context.
  98. *
  99. * @param sessionContext The session context
  100. * @param msg Message created with `KernelMessage.createMessage`
  101. */
  102. export function emitIopubMessage(
  103. context: ISessionContext,
  104. msg: KernelMessage.IIOPubMessage
  105. ): void {
  106. const kernel = context!.session!.kernel!;
  107. const msgId = Private.lastMessageProperty.get(kernel);
  108. (msg.parent_header as any).session = kernel.clientId;
  109. (msg.parent_header as any).msg_id = msgId;
  110. (kernel.iopubMessage as any).emit(msg);
  111. }
  112. /**
  113. * Create a session context given a partial session model.
  114. *
  115. * @param model The session model to use.
  116. */
  117. export function createSimpleSessionContext(
  118. model: Private.RecursivePartial<Session.IModel> = {}
  119. ): ISessionContext {
  120. const kernel = new KernelMock({ model: model?.kernel || {} });
  121. const session = new SessionConnectionMock({ model }, kernel);
  122. return new SessionContextMock({}, session);
  123. }
  124. /**
  125. * Clone a kernel connection.
  126. */
  127. export function cloneKernel(
  128. kernel: Kernel.IKernelConnection
  129. ): Kernel.IKernelConnection {
  130. return (kernel as any).clone();
  131. }
  132. /**
  133. * A mock kernel object.
  134. *
  135. * @param model The model of the kernel
  136. */
  137. export const KernelMock = jest.fn<
  138. Kernel.IKernelConnection,
  139. [Private.RecursivePartial<Kernel.IKernelConnection.IOptions>]
  140. >(options => {
  141. const model = { id: 'foo', name: DEFAULT_NAME, ...options.model };
  142. options = {
  143. clientId: UUID.uuid4(),
  144. username: UUID.uuid4(),
  145. ...options,
  146. model
  147. };
  148. let executionCount = 0;
  149. const spec = Private.kernelSpecForKernelName(model.name)!;
  150. const thisObject: Kernel.IKernelConnection = {
  151. ...jest.requireActual('@jupyterlab/services'),
  152. ...options,
  153. ...model,
  154. status: 'idle',
  155. spec: Promise.resolve(spec),
  156. dispose: jest.fn(),
  157. clone: jest.fn(() => {
  158. const newKernel = Private.cloneKernel(options);
  159. newKernel.iopubMessage.connect((_, args) => {
  160. iopubMessageSignal.emit(args);
  161. });
  162. newKernel.statusChanged.connect((_, args) => {
  163. (thisObject as any).status = args;
  164. statusChangedSignal.emit(args);
  165. });
  166. return newKernel;
  167. }),
  168. info: Promise.resolve(Private.getInfo(model!.name!)),
  169. shutdown: jest.fn(() => Promise.resolve(void 0)),
  170. requestHistory: jest.fn(() => {
  171. const historyReply = KernelMessage.createMessage({
  172. channel: 'shell',
  173. msgType: 'history_reply',
  174. session: options.clientId!,
  175. username: options.username!,
  176. content: {
  177. history: [],
  178. status: 'ok'
  179. }
  180. });
  181. return Promise.resolve(historyReply);
  182. }),
  183. restart: jest.fn(() => Promise.resolve(void 0)),
  184. requestExecute: jest.fn(options => {
  185. const msgId = UUID.uuid4();
  186. executionCount++;
  187. Private.lastMessageProperty.set(thisObject, msgId);
  188. const msg = KernelMessage.createMessage({
  189. channel: 'iopub',
  190. msgType: 'execute_input',
  191. session: thisObject.clientId,
  192. username: thisObject.username,
  193. msgId,
  194. content: {
  195. code: options.code,
  196. execution_count: executionCount
  197. }
  198. });
  199. iopubMessageSignal.emit(msg);
  200. const reply = KernelMessage.createMessage<KernelMessage.IExecuteReplyMsg>(
  201. {
  202. channel: 'shell',
  203. msgType: 'execute_reply',
  204. session: thisObject.clientId,
  205. username: thisObject.username,
  206. msgId,
  207. content: {
  208. user_expressions: {},
  209. execution_count: executionCount,
  210. status: 'ok'
  211. }
  212. }
  213. );
  214. return new MockShellFuture(reply) as Kernel.IShellFuture<
  215. KernelMessage.IExecuteRequestMsg,
  216. KernelMessage.IExecuteReplyMsg
  217. >;
  218. })
  219. } as any; // FIXME: fix the typing error this any cast is ignoring
  220. // Add signals.
  221. const iopubMessageSignal = new Signal<
  222. Kernel.IKernelConnection,
  223. KernelMessage.IIOPubMessage
  224. >(thisObject);
  225. const statusChangedSignal = new Signal<
  226. Kernel.IKernelConnection,
  227. Kernel.Status
  228. >(thisObject);
  229. (thisObject as any).statusChanged = statusChangedSignal;
  230. (thisObject as any).iopubMessage = iopubMessageSignal;
  231. return thisObject;
  232. });
  233. /**
  234. * A mock session connection.
  235. *
  236. * @param options Addition session options to use
  237. * @param model A session model to use
  238. */
  239. export const SessionConnectionMock = jest.fn<
  240. Session.ISessionConnection,
  241. [
  242. Private.RecursivePartial<Session.ISessionConnection.IOptions>,
  243. Kernel.IKernelConnection | null
  244. ]
  245. >((options, kernel) => {
  246. const name = kernel?.name || options.model?.kernel?.name || DEFAULT_NAME;
  247. kernel = kernel || new KernelMock({ model: { name } });
  248. const model = {
  249. path: 'foo',
  250. type: 'notebook',
  251. name: 'foo',
  252. ...options.model,
  253. kernel: kernel!.model
  254. };
  255. const thisObject: Session.ISessionConnection = {
  256. ...jest.requireActual('@jupyterlab/services'),
  257. id: UUID.uuid4(),
  258. ...options,
  259. model,
  260. ...model,
  261. kernel,
  262. dispose: jest.fn(),
  263. changeKernel: jest.fn(partialModel => {
  264. return Private.changeKernel(kernel!, partialModel!);
  265. }),
  266. shutdown: jest.fn(() => Promise.resolve(void 0)),
  267. setPath: jest.fn(path => {
  268. (thisObject as any).path = path;
  269. propertyChangedSignal.emit('path');
  270. return Promise.resolve();
  271. }),
  272. setName: jest.fn(name => {
  273. (thisObject as any).name = name;
  274. propertyChangedSignal.emit('name');
  275. return Promise.resolve();
  276. }),
  277. setType: jest.fn(type => {
  278. (thisObject as any).type = type;
  279. propertyChangedSignal.emit('type');
  280. return Promise.resolve();
  281. })
  282. } as any; // FIXME: fix the typing error this any cast is ignoring
  283. const disposedSignal = new Signal<Session.ISessionConnection, undefined>(
  284. thisObject
  285. );
  286. const propertyChangedSignal = new Signal<
  287. Session.ISessionConnection,
  288. 'path' | 'name' | 'type'
  289. >(thisObject);
  290. const statusChangedSignal = new Signal<
  291. Session.ISessionConnection,
  292. Kernel.Status
  293. >(thisObject);
  294. const connectionStatusChangedSignal = new Signal<
  295. Session.ISessionConnection,
  296. Kernel.ConnectionStatus
  297. >(thisObject);
  298. const kernelChangedSignal = new Signal<
  299. Session.ISessionConnection,
  300. Session.ISessionConnection.IKernelChangedArgs
  301. >(thisObject);
  302. const iopubMessageSignal = new Signal<
  303. Session.ISessionConnection,
  304. KernelMessage.IIOPubMessage
  305. >(thisObject);
  306. const unhandledMessageSignal = new Signal<
  307. Session.ISessionConnection,
  308. KernelMessage.IMessage
  309. >(thisObject);
  310. kernel!.iopubMessage.connect((_, args) => {
  311. iopubMessageSignal.emit(args);
  312. }, thisObject);
  313. kernel!.statusChanged.connect((_, args) => {
  314. statusChangedSignal.emit(args);
  315. }, thisObject);
  316. (thisObject as any).disposed = disposedSignal;
  317. (thisObject as any).connectionStatusChanged = connectionStatusChangedSignal;
  318. (thisObject as any).propertyChanged = propertyChangedSignal;
  319. (thisObject as any).statusChanged = statusChangedSignal;
  320. (thisObject as any).kernelChanged = kernelChangedSignal;
  321. (thisObject as any).iopubMessage = iopubMessageSignal;
  322. (thisObject as any).unhandledMessage = unhandledMessageSignal;
  323. return thisObject;
  324. });
  325. /**
  326. * A mock session context.
  327. *
  328. * @param session The session connection object to use
  329. */
  330. export const SessionContextMock = jest.fn<
  331. ISessionContext,
  332. [Partial<SessionContext.IOptions>, Session.ISessionConnection | null]
  333. >((options, connection) => {
  334. const session =
  335. connection ||
  336. new SessionConnectionMock(
  337. {
  338. model: {
  339. path: options.path || '',
  340. type: options.type || '',
  341. name: options.name || ''
  342. }
  343. },
  344. null
  345. );
  346. const thisObject: ISessionContext = {
  347. ...jest.requireActual('@jupyterlab/apputils'),
  348. ...options,
  349. path: session.path,
  350. type: session.type,
  351. name: session.name,
  352. kernel: session.kernel,
  353. session,
  354. dispose: jest.fn(),
  355. initialize: jest.fn(() => Promise.resolve()),
  356. ready: Promise.resolve(),
  357. changeKernel: jest.fn(partialModel => {
  358. return Private.changeKernel(
  359. session.kernel || Private.RUNNING_KERNELS[0],
  360. partialModel!
  361. );
  362. }),
  363. shutdown: jest.fn(() => Promise.resolve())
  364. } as any; // FIXME: fix the typing error this any cast is ignoring
  365. const disposedSignal = new Signal<ISessionContext, undefined>(thisObject);
  366. const propertyChangedSignal = new Signal<
  367. ISessionContext,
  368. 'path' | 'name' | 'type'
  369. >(thisObject);
  370. const statusChangedSignal = new Signal<ISessionContext, Kernel.Status>(
  371. thisObject
  372. );
  373. const kernelChangedSignal = new Signal<
  374. ISessionContext,
  375. Session.ISessionConnection.IKernelChangedArgs
  376. >(thisObject);
  377. const iopubMessageSignal = new Signal<
  378. ISessionContext,
  379. KernelMessage.IIOPubMessage
  380. >(thisObject);
  381. session!.statusChanged.connect((_, args) => {
  382. statusChangedSignal.emit(args);
  383. }, thisObject);
  384. session!.iopubMessage.connect((_, args) => {
  385. iopubMessageSignal.emit(args);
  386. });
  387. session!.kernelChanged.connect((_, args) => {
  388. kernelChangedSignal.emit(args);
  389. });
  390. (thisObject as any).statusChanged = statusChangedSignal;
  391. (thisObject as any).kernelChanged = kernelChangedSignal;
  392. (thisObject as any).iopubMessage = iopubMessageSignal;
  393. (thisObject as any).propertyChanged = propertyChangedSignal;
  394. (thisObject as any).disposed = disposedSignal;
  395. (thisObject as any).session = session;
  396. return thisObject;
  397. });
  398. /**
  399. * A mock contents manager.
  400. */
  401. export const ContentsManagerMock = jest.fn<Contents.IManager, []>(() => {
  402. const files = new Map<string, Contents.IModel>();
  403. const dummy = new ContentsManager();
  404. const checkpoints = new Map<string, Contents.ICheckpointModel>();
  405. const checkPointContent = new Map<string, string>();
  406. const baseModel = Private.createFile({ type: 'directory' });
  407. files.set('', { ...baseModel, path: '', name: '' });
  408. const thisObject: Contents.IManager = {
  409. ...jest.requireActual('@jupyterlab/services'),
  410. newUntitled: jest.fn(options => {
  411. const model = Private.createFile(options || {});
  412. files.set(model.path, model);
  413. fileChangedSignal.emit({
  414. type: 'new',
  415. oldValue: null,
  416. newValue: model
  417. });
  418. return Promise.resolve(model);
  419. }),
  420. createCheckpoint: jest.fn(path => {
  421. const lastModified = new Date().toISOString();
  422. const data = { id: UUID.uuid4(), last_modified: lastModified };
  423. checkpoints.set(path, data);
  424. checkPointContent.set(path, files.get(path)?.content);
  425. return Promise.resolve(data);
  426. }),
  427. listCheckpoints: jest.fn(path => {
  428. const p = checkpoints.get(path);
  429. if (p !== undefined) {
  430. return Promise.resolve([p]);
  431. }
  432. return Promise.resolve([]);
  433. }),
  434. deleteCheckpoint: jest.fn(path => {
  435. if (!checkpoints.has(path)) {
  436. return Private.makeResponseError(404);
  437. }
  438. checkpoints.delete(path);
  439. return Promise.resolve();
  440. }),
  441. restoreCheckpoint: jest.fn(path => {
  442. if (!checkpoints.has(path)) {
  443. return Private.makeResponseError(404);
  444. }
  445. (files.get(path) as any).content = checkPointContent.get(path);
  446. return Promise.resolve();
  447. }),
  448. getModelDBFactory: jest.fn(() => {
  449. return null;
  450. }),
  451. normalize: jest.fn(path => {
  452. return dummy.normalize(path);
  453. }),
  454. localPath: jest.fn(path => {
  455. return dummy.localPath(path);
  456. }),
  457. resolvePath: jest.fn((root, path) => {
  458. return dummy.resolvePath(root, path);
  459. }),
  460. get: jest.fn((path, options) => {
  461. path = Private.fixSlash(path);
  462. if (!files.has(path)) {
  463. return Private.makeResponseError(404);
  464. }
  465. const model = files.get(path)!;
  466. if (model.type === 'directory') {
  467. if (options?.content !== false) {
  468. const content: Contents.IModel[] = [];
  469. files.forEach(fileModel => {
  470. if (PathExt.dirname(fileModel.path) == model.path) {
  471. content.push(fileModel);
  472. }
  473. });
  474. return Promise.resolve({ ...model, content });
  475. }
  476. return Promise.resolve(model);
  477. }
  478. if (options?.content != false) {
  479. return Promise.resolve(model);
  480. }
  481. return Promise.resolve({ ...model, content: '' });
  482. }),
  483. driveName: jest.fn(path => {
  484. return dummy.driveName(path);
  485. }),
  486. rename: jest.fn((oldPath, newPath) => {
  487. oldPath = Private.fixSlash(oldPath);
  488. newPath = Private.fixSlash(newPath);
  489. if (!files.has(oldPath)) {
  490. return Private.makeResponseError(404);
  491. }
  492. const oldValue = files.get(oldPath)!;
  493. files.delete(oldPath);
  494. const name = PathExt.basename(newPath);
  495. const newValue = { ...oldValue, name, path: newPath };
  496. files.set(newPath, newValue);
  497. fileChangedSignal.emit({
  498. type: 'rename',
  499. oldValue,
  500. newValue
  501. });
  502. return Promise.resolve(newValue);
  503. }),
  504. delete: jest.fn(path => {
  505. path = Private.fixSlash(path);
  506. if (!files.has(path)) {
  507. return Private.makeResponseError(404);
  508. }
  509. const oldValue = files.get(path)!;
  510. files.delete(path);
  511. fileChangedSignal.emit({
  512. type: 'delete',
  513. oldValue,
  514. newValue: null
  515. });
  516. return Promise.resolve(void 0);
  517. }),
  518. save: jest.fn((path, options) => {
  519. if (path == 'readonly.txt') {
  520. return Private.makeResponseError(403);
  521. }
  522. path = Private.fixSlash(path);
  523. const timeStamp = new Date().toISOString();
  524. if (files.has(path)) {
  525. files.set(path, {
  526. ...files.get(path)!,
  527. ...options,
  528. last_modified: timeStamp
  529. });
  530. } else {
  531. files.set(path, {
  532. path,
  533. name: PathExt.basename(path),
  534. content: '',
  535. writable: true,
  536. created: timeStamp,
  537. type: 'file',
  538. format: 'text',
  539. mimetype: 'plain/text',
  540. ...options,
  541. last_modified: timeStamp
  542. });
  543. }
  544. fileChangedSignal.emit({
  545. type: 'save',
  546. oldValue: null,
  547. newValue: files.get(path)!
  548. });
  549. return Promise.resolve(files.get(path)!);
  550. }),
  551. getDownloadUrl: jest.fn(path => {
  552. return dummy.getDownloadUrl(path);
  553. }),
  554. addDrive: jest.fn(drive => {
  555. dummy.addDrive(drive);
  556. }),
  557. dispose: jest.fn()
  558. };
  559. const fileChangedSignal = new Signal<
  560. Contents.IManager,
  561. Contents.IChangedArgs
  562. >(thisObject);
  563. (thisObject as any).fileChanged = fileChangedSignal;
  564. return thisObject;
  565. });
  566. /**
  567. * A mock sessions manager.
  568. */
  569. export const SessionManagerMock = jest.fn<Session.IManager, []>(() => {
  570. let sessions: Session.IModel[] = [];
  571. const thisObject: Session.IManager = {
  572. ...jest.requireActual('@jupyterlab/services'),
  573. ready: Promise.resolve(void 0),
  574. isReady: true,
  575. startNew: jest.fn(options => {
  576. const session = new SessionConnectionMock({ model: options }, null);
  577. sessions.push(session.model);
  578. runningChangedSignal.emit(sessions);
  579. return Promise.resolve(session);
  580. }),
  581. connectTo: jest.fn(options => {
  582. return new SessionConnectionMock(options, null);
  583. }),
  584. stopIfNeeded: jest.fn(path => {
  585. const length = sessions.length;
  586. sessions = sessions.filter(model => model.path !== path);
  587. if (sessions.length !== length) {
  588. runningChangedSignal.emit(sessions);
  589. }
  590. return Promise.resolve(void 0);
  591. }),
  592. refreshRunning: jest.fn(() => Promise.resolve(void 0)),
  593. running: jest.fn(() => new ArrayIterator(sessions))
  594. };
  595. const runningChangedSignal = new Signal<Session.IManager, Session.IModel[]>(
  596. thisObject
  597. );
  598. (thisObject as any).runningChanged = runningChangedSignal;
  599. return thisObject;
  600. });
  601. /**
  602. * A mock kernel specs manager
  603. */
  604. export const KernelSpecManagerMock = jest.fn<KernelSpec.IManager, []>(() => {
  605. const thisObject: KernelSpec.IManager = {
  606. ...jest.requireActual('@jupyterlab/services'),
  607. specs: { default: DEFAULT_NAME, kernelspecs: KERNELSPECS },
  608. isReady: true,
  609. ready: Promise.resolve(void 0),
  610. refreshSpecs: jest.fn(() => Promise.resolve(void 0))
  611. };
  612. return thisObject;
  613. });
  614. /**
  615. * A mock service manager.
  616. */
  617. export const ServiceManagerMock = jest.fn<ServiceManager.IManager, []>(() => {
  618. const thisObject: ServiceManager.IManager = {
  619. ...jest.requireActual('@jupyterlab/services'),
  620. ready: Promise.resolve(void 0),
  621. isReady: true,
  622. contents: new ContentsManagerMock(),
  623. sessions: new SessionManagerMock(),
  624. kernelspecs: new KernelSpecManagerMock(),
  625. dispose: jest.fn()
  626. };
  627. return thisObject;
  628. });
  629. /**
  630. * A mock kernel shell future.
  631. */
  632. export const MockShellFuture = jest.fn<
  633. Kernel.IShellFuture,
  634. [KernelMessage.IShellMessage]
  635. >((result: KernelMessage.IShellMessage) => {
  636. const thisObject: Kernel.IShellFuture = {
  637. ...jest.requireActual('@jupyterlab/services'),
  638. dispose: jest.fn(),
  639. done: Promise.resolve(result)
  640. };
  641. return thisObject;
  642. });
  643. /**
  644. * Create a context for a file.
  645. */
  646. export async function createFileContext(
  647. startKernel = false,
  648. manager?: ServiceManager.IManager
  649. ): Promise<Context> {
  650. const path = UUID.uuid4() + '.txt';
  651. manager = manager || new ServiceManagerMock();
  652. const factory = new TextModelFactory();
  653. const context = new Context({
  654. manager: manager || new ServiceManagerMock(),
  655. factory,
  656. path,
  657. kernelPreference: {
  658. shouldStart: startKernel,
  659. canStart: startKernel,
  660. autoStartDefault: startKernel
  661. }
  662. });
  663. await context.initialize(true);
  664. await context.sessionContext.initialize();
  665. return context;
  666. }
  667. /**
  668. * A namespace for module private data.
  669. */
  670. namespace Private {
  671. export function flattenArray<T>(arr: T[][]): T[] {
  672. const result: T[] = [];
  673. arr.forEach(innerArr => {
  674. innerArr.forEach(elem => {
  675. result.push(elem);
  676. });
  677. });
  678. return result;
  679. }
  680. export type RecursivePartial<T> = {
  681. [P in keyof T]?: RecursivePartial<T[P]>;
  682. };
  683. export function createFile(
  684. options?: Contents.ICreateOptions
  685. ): Contents.IModel {
  686. options = options || {};
  687. let name = UUID.uuid4();
  688. switch (options.type) {
  689. case 'directory':
  690. name = `Untitled Folder_${name}`;
  691. break;
  692. case 'notebook':
  693. name = `Untitled_${name}.ipynb`;
  694. break;
  695. default:
  696. name = `untitled_${name}${options.ext || '.txt'}`;
  697. }
  698. const path = PathExt.join(options.path || '', name);
  699. let content = '';
  700. if (options.type === 'notebook') {
  701. content = JSON.stringify({});
  702. }
  703. const timeStamp = new Date().toISOString();
  704. return {
  705. path,
  706. content,
  707. name,
  708. last_modified: timeStamp,
  709. writable: true,
  710. created: timeStamp,
  711. type: options.type || 'file',
  712. format: 'text',
  713. mimetype: 'plain/text'
  714. };
  715. }
  716. export function fixSlash(path: string): string {
  717. if (path.endsWith('/')) {
  718. path = path.slice(0, path.length - 1);
  719. }
  720. return path;
  721. }
  722. export function makeResponseError<T>(status: number): Promise<T> {
  723. const resp = new Response(void 0, { status });
  724. return Promise.reject(new ServerConnection.ResponseError(resp));
  725. }
  726. export function cloneKernel(
  727. options: RecursivePartial<Kernel.IKernelConnection.IOptions>
  728. ): Kernel.IKernelConnection {
  729. return new KernelMock({ ...options, clientId: UUID.uuid4() });
  730. }
  731. // Get the kernel spec for kernel name
  732. export function kernelSpecForKernelName(name: string) {
  733. return KERNELSPECS[name];
  734. }
  735. // Get the kernel info for kernel name
  736. export function getInfo(name: string): KernelMessage.IInfoReply {
  737. return {
  738. protocol_version: '1',
  739. implementation: 'foo',
  740. implementation_version: '1',
  741. language_info: {
  742. version: '1',
  743. name
  744. },
  745. banner: 'hello, world!',
  746. help_links: [],
  747. status: 'ok'
  748. };
  749. }
  750. export function changeKernel(
  751. kernel: Kernel.IKernelConnection,
  752. partialModel: Partial<Kernel.IModel>
  753. ): Promise<Kernel.IModel> {
  754. if (partialModel.id) {
  755. const kernelIdx = KERNEL_MODELS.findIndex(model => {
  756. return model.id === partialModel.id;
  757. });
  758. if (kernelIdx !== -1) {
  759. (kernel.model as any) = RUNNING_KERNELS[kernelIdx].model;
  760. (kernel.id as any) = partialModel.id;
  761. return Promise.resolve(RUNNING_KERNELS[kernelIdx]);
  762. } else {
  763. throw new Error(
  764. `Unable to change kernel to one with id: ${partialModel.id}`
  765. );
  766. }
  767. } else if (partialModel.name) {
  768. const kernelIdx = KERNEL_MODELS.findIndex(model => {
  769. return model.name === partialModel.name;
  770. });
  771. if (kernelIdx !== -1) {
  772. (kernel.model as any) = RUNNING_KERNELS[kernelIdx].model;
  773. (kernel.id as any) = partialModel.id;
  774. return Promise.resolve(RUNNING_KERNELS[kernelIdx]);
  775. } else {
  776. throw new Error(
  777. `Unable to change kernel to one with name: ${partialModel.name}`
  778. );
  779. }
  780. } else {
  781. throw new Error(`Unable to change kernel`);
  782. }
  783. }
  784. // This list of running kernels simply mirrors the KERNEL_MODELS and KERNELSPECS lists
  785. export const RUNNING_KERNELS: Kernel.IKernelConnection[] = KERNEL_MODELS.map(
  786. (model, _) => {
  787. return new KernelMock({ model });
  788. }
  789. );
  790. export const lastMessageProperty = new AttachedProperty<
  791. Kernel.IKernelConnection,
  792. string
  793. >({
  794. name: 'lastMessageId',
  795. create: () => ''
  796. });
  797. }