mode.spec.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. /// <reference types="../typings/codemirror/codemirror" />
  4. import { Mode } from '@jupyterlab/codemirror';
  5. import CodeMirror from 'codemirror';
  6. function fakeMode(name: string) {
  7. return {
  8. mode: name,
  9. ext: [name],
  10. mime: `text/${name}`,
  11. name: name.toUpperCase()
  12. };
  13. }
  14. /**
  15. * State names used in simple mode.
  16. */
  17. type S = 'start' | 'comment';
  18. /**
  19. * Tokens used in simple mode.
  20. * */
  21. enum T {
  22. AM = 'atom',
  23. AT = 'attribute',
  24. BE = 'builtin.em',
  25. BI = 'builtin',
  26. BK = 'bracket',
  27. CM = 'comment',
  28. DF = 'def',
  29. HL = 'header',
  30. KW = 'keyword',
  31. MT = 'meta',
  32. NB = 'number',
  33. OP = 'operator',
  34. PC = 'punctuation',
  35. PR = 'property',
  36. SE = 'string.em',
  37. SH = 'string.header',
  38. SS = 'string.strong',
  39. SSE = 'string.strong.em',
  40. S2 = 'string-2',
  41. ST = 'string',
  42. TG = 'tag',
  43. V1 = 'variable',
  44. V2 = 'variable-2',
  45. V3 = 'variable-3'
  46. }
  47. /**
  48. * Simple mode states from CodeMirror demo:
  49. * from https://codemirror.net/demo/simplemode.html
  50. */
  51. const FAKE_SIMPLE_STATES: CodeMirror.TSimpleTopState<S, T> = {
  52. // The start state contains the rules that are initially used
  53. start: [
  54. // The regex matches the token, the token property contains the type
  55. { regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: T.ST },
  56. // You can match multiple tokens at once. Note that the captured
  57. // groups must span the whole string in this case
  58. { regex: /(function)(\s+)([a-z$][\w$]*)/, token: [T.KW, null, T.V2] },
  59. // Rules are matched in the order in which they appear, so there is
  60. // no ambiguity between this one and the one above
  61. {
  62. regex: /(?:function|var|return|if|for|while|else|do|this)\b/,
  63. token: T.KW
  64. },
  65. { regex: /true|false|null|undefined/, token: T.AT },
  66. {
  67. regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
  68. token: T.NB
  69. },
  70. { regex: /\/\/.*/, token: T.CM },
  71. { regex: /\/(?:[^\\]|\\.)*?\//, token: T.V3 },
  72. // A next property will cause the mode to move to a different state
  73. { regex: /\/\*/, token: T.CM, next: T.CM },
  74. { regex: /[-+\/*=<>!]+/, token: T.OP },
  75. // indent and dedent properties guide autoindentation
  76. { regex: /[\{\[\(]/, indent: true },
  77. { regex: /[\}\]\)]/, dedent: true },
  78. { regex: /[a-z$][\w$]*/, token: T.V1 },
  79. // You can embed other modes with the mode property. This rule
  80. // causes all code between << and >> to be highlighted with the XML
  81. // mode.
  82. { regex: /<</, token: T.MT, mode: { spec: 'xml', end: />>/ } }
  83. ],
  84. // The multi-line comment state.
  85. comment: [
  86. { regex: /.*?\*\//, token: T.CM, next: 'start' },
  87. { regex: /.*/, token: T.CM }
  88. ]
  89. };
  90. FAKE_SIMPLE_STATES.meta = {
  91. dontIndentStates: ['comment']
  92. };
  93. describe('Mode', () => {
  94. describe('#ensure', () => {
  95. it('should load a defined spec', async () => {
  96. CodeMirror.modeInfo.push(fakeMode('foo'));
  97. CodeMirror.defineMode('foo', () => {
  98. return {};
  99. });
  100. const spec = (await Mode.ensure('text/foo'))!;
  101. expect(spec.name).toBe('FOO');
  102. });
  103. it('should load a bundled spec', async () => {
  104. const spec = (await Mode.ensure('application/json'))!;
  105. expect(spec.name).toBe('JSON');
  106. });
  107. it('should add a spec loader', async () => {
  108. let called = 0;
  109. let loaded = 0;
  110. Mode.addSpecLoader(async spec => {
  111. called++;
  112. if (spec.mode !== 'bar') {
  113. return false;
  114. }
  115. loaded++;
  116. return true;
  117. }, 42);
  118. CodeMirror.modeInfo.push(fakeMode('bar'));
  119. let spec = await Mode.ensure('bar');
  120. expect(called).toBe(1);
  121. expect(loaded).toBe(1);
  122. expect(spec!.name).toBe('BAR');
  123. spec = await Mode.ensure('python');
  124. expect(called).toBe(1);
  125. expect(loaded).toBe(1);
  126. try {
  127. spec = await Mode.ensure('APL');
  128. } catch (err) {
  129. // apparently one cannot use webpack `require` in jest
  130. }
  131. expect(called).toBe(2);
  132. expect(loaded).toBe(1);
  133. });
  134. it('should default to plain text', async () => {
  135. const spec = (await Mode.ensure('this is not a mode'))!;
  136. expect(spec.name).toBe('Plain Text');
  137. });
  138. it('should create a simple mode', async () => {
  139. CodeMirror.modeInfo.push(fakeMode('baz'));
  140. CodeMirror.defineSimpleMode('baz', FAKE_SIMPLE_STATES);
  141. const spec = (await Mode.ensure('text/baz'))!;
  142. expect(spec.name).toBe('BAZ');
  143. });
  144. });
  145. });