qword 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/ChangeLog +14 -0
  2. package/assets/style.css +10 -10
  3. package/client/_clipboard.js +30 -33
  4. package/client/_clipboard.spec.js +160 -0
  5. package/client/content.d.ts +8 -0
  6. package/client/content.js +14 -0
  7. package/client/content.spec.js +69 -0
  8. package/client/create.d.ts +32 -0
  9. package/client/create.js +95 -0
  10. package/client/create.spec.js +192 -0
  11. package/client/decorations.d.ts +40 -0
  12. package/client/decorations.js +110 -0
  13. package/client/decorations.spec.js +135 -0
  14. package/client/dom.d.ts +7 -0
  15. package/client/dom.js +17 -0
  16. package/client/dom.spec.js +78 -0
  17. package/client/editor.d.ts +72 -0
  18. package/client/editor.js +164 -0
  19. package/client/editor.spec.js +307 -0
  20. package/client/events.d.ts +7 -0
  21. package/client/events.js +8 -0
  22. package/client/events.spec.js +100 -0
  23. package/client/highlight.d.ts +3 -0
  24. package/client/highlight.js +82 -0
  25. package/client/highlight.spec.js +28 -0
  26. package/client/history.d.ts +4 -0
  27. package/client/history.js +16 -0
  28. package/client/history.spec.js +57 -0
  29. package/client/index.d.ts +60 -0
  30. package/client/index.js +37 -0
  31. package/client/loadremote.js +10 -4
  32. package/client/options.d.ts +30 -0
  33. package/client/options.js +59 -0
  34. package/client/options.spec.js +157 -0
  35. package/client/position.d.ts +21 -0
  36. package/client/position.js +33 -0
  37. package/client/position.spec.js +176 -0
  38. package/client/qword.js +141 -416
  39. package/client/scroll.d.ts +10 -0
  40. package/client/scroll.js +9 -0
  41. package/client/scroll.spec.js +58 -0
  42. package/client/types.d.ts +24 -0
  43. package/dist/qword.js +175 -1
  44. package/dist/qword.js.map +1 -1
  45. package/dist-dev/qword.js +544 -660
  46. package/package.json +30 -14
  47. package/server/edit.js +8 -3
  48. package/server/edit.spec.js +101 -0
  49. package/test/errors.ts +21 -0
  50. package/test/types.ts +134 -0
  51. package/client/codemirror/show-trailing.js +0 -54
  52. package/client/codemirror/use-soft-tabs.js +0 -38
  53. package/client/set-key-map.js +0 -3
  54. package/dist/2b22a33a0a80bb926bbc.png +0 -1
  55. package/dist/8e9019bd86255ac9a618.png +0 -1
  56. package/dist/codemirror.js +0 -2
  57. package/dist/codemirror.js.map +0 -1
  58. package/dist/dword.js +0 -2
  59. package/dist/dword.js.map +0 -1
  60. package/dist-dev/2b22a33a0a80bb926bbc.png +0 -1
  61. package/dist-dev/8e9019bd86255ac9a618.png +0 -1
  62. package/dist-dev/codemirror.js +0 -104
  63. package/dist-dev/dword.js +0 -960
  64. package/modules/codemirror-emmet/dist/emmet.js +0 -48661
  65. package/modules/codemirror-emmet/dist/emmet.min.js +0 -8
  66. package/modules/codemirror-emmet/dist/emmetPlugin.js +0 -840
  67. package/modules/codemirror-emmet/dist/emmetPlugin.min.js +0 -1
  68. package/modules/codemirror-emmet/dist/index.html +0 -46
  69. package/modules/jshint/README.md +0 -114
  70. package/modules/jshint/dist/jshint-rhino.js +0 -32143
  71. package/modules/jshint/dist/jshint.js +0 -32141
  72. package/modules/jshint/package.json +0 -77
package/ChangeLog CHANGED
@@ -1,3 +1,17 @@
1
+ 2026.09.05, v1.1.0
2
+
3
+ fix:
4
+ - 34a981c client: clipboard
5
+
6
+ feature:
7
+ - 83e40e6 qword: cssnano v9.0.2
8
+ - 2f29f72 types: add
9
+ - 58fce83 migrate to rspack
10
+ - 03bb806 qword: @lezer/highlight v1.2.3
11
+ - c5cf54a qword: superc8 v12.7.0
12
+ - 51d8bc5 editor: move out
13
+ - 3d791d7 qword: @putout/plugin-convert-webpack-to-rspack v1.0.1
14
+
1
15
  2026.05.18, v1.0.3
2
16
 
3
17
  fix:
package/assets/style.css CHANGED
@@ -1,18 +1,18 @@
1
1
  @font-face {
2
2
  font-family: 'Droid Sans Mono';
3
- src: url(https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJTwtzT4qNq-faudv5qbO9-U.eot);
3
+ src: url('https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJTwtzT4qNq-faudv5qbO9-U.eot');
4
4
  src:
5
5
  local('Droid Sans Mono'),
6
6
  local('DroidSansMono'),
7
- url(/font/DroidSansMono.eot) format('embedded-opentype'),
8
- url(https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJTwtzT4qNq-faudv5qbO9-U.eot?#iefix)
9
- format('embedded-opentype'),
10
- url(https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJTwtzT4qNq-faudv5qbO9-U.eot)
11
- format('embedded-opentype'),
12
- url(/font/DroidSansMono.woff2) format('woff2'),
13
- url(/font/DroidSansMono.woff) format('woff'),
14
- url(https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff)
15
- format('woff'),
7
+ url('/font/DroidSansMono.eot') format('embedded-opentype'),
8
+ url('https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJTwtzT4qNq-faudv5qbO9-U.eot?#iefix')
9
+ format('embedded-opentype'),
10
+ url('https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJTwtzT4qNq-faudv5qbO9-U.eot')
11
+ format('embedded-opentype'),
12
+ url('/font/DroidSansMono.woff2') format('woff2'),
13
+ url('/font/DroidSansMono.woff') format('woff'),
14
+ url('https://themes.googleusercontent.com/static/fonts/droidsansmono/v5/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff')
15
+ format('woff'),
16
16
  local('Consolas');
17
17
  font-style: normal;
18
18
  font-weight: 400;
@@ -1,53 +1,50 @@
1
1
  import clipboard from '@cloudcmd/clipboard';
2
- import createElement from '@cloudcmd/create-element';
3
2
  import once from 'once';
4
- import _showMessageOnce from './show-message.js';
3
+ import showMessage from './show-message.js';
5
4
 
6
- const resolve = Promise.resolve.bind(Promise);
7
- const reject = Promise.reject.bind(Promise);
8
- const showMessageOnce = once(_showMessageOnce);
5
+ const showMessageOnce = once(showMessage);
9
6
 
10
- export default (cmd) => {
7
+ export default function clipboardCommand(command) {
8
+ const {_view, _story} = this;
11
9
  const NAME = 'editor-clipboard';
12
- const {_Ace, _story} = this;
13
10
 
14
- const value = _Ace.getSelection('\n');
15
- const insert = (a) => _Ace
16
- .getDoc()
17
- .replaceSelection(a);
11
+ const getSelected = () => {
12
+ const {from, to} = _view.state.selection.main;
13
+ return _view.state.sliceDoc(from, to);
14
+ };
15
+
16
+ const insert = (text) => {
17
+ const {from, to} = _view.state.selection.main;
18
+
19
+ _view.dispatch({
20
+ changes: {
21
+ from,
22
+ to,
23
+ insert: text,
24
+ },
25
+ });
26
+ };
18
27
 
19
- if (cmd === 'copy') {
28
+ if (command === 'copy') {
29
+ const value = getSelected();
20
30
  _story.setData(NAME, value);
31
+
21
32
  return clipboard.writeText(value);
22
33
  }
23
34
 
24
- if (cmd === 'cut') {
35
+ if (command === 'cut') {
36
+ const value = getSelected();
25
37
  _story.setData(NAME, value);
26
- return cut(value) ? resolve() : reject();
38
+ insert('');
39
+
40
+ return clipboard.writeText(value);
27
41
  }
28
42
 
29
43
  return clipboard
30
44
  .readText()
31
45
  .then(insert)
32
46
  .catch(() => {
33
- showMessageOnce('Could not paste from clipboard. Inner buffer used.');
34
-
35
- const value = _story.getData(NAME);
36
-
37
- insert(value);
47
+ showMessageOnce.call(this, 'Could not paste from clipboard. Inner buffer used.');
48
+ insert(_story.getData(NAME));
38
49
  });
39
- };
40
-
41
- function cut(value) {
42
- const textarea = createElement('textarea', {
43
- value,
44
- });
45
-
46
- textarea.select();
47
-
48
- const result = document.execCommand('cut');
49
-
50
- document.body.removeChild(textarea);
51
-
52
- return result;
53
50
  }
@@ -0,0 +1,160 @@
1
+ import {test, stub} from 'supertape';
2
+
3
+ test('clipboard: copy stores value in story', (t) => {
4
+ const story = createStoryStub();
5
+ const view = createViewStub('hello world');
6
+
7
+ const clipboardFn = createClipboard({
8
+ story,
9
+ view,
10
+ });
11
+
12
+ clipboardFn('copy');
13
+
14
+ t.ok(story.setData.called, 'story.setData should be called');
15
+ t.end();
16
+ });
17
+
18
+ test('clipboard: copy calls clipboard.writeText', async (t) => {
19
+ const story = createStoryStub();
20
+ const view = createViewStub('selected text');
21
+
22
+ const writeTextStub = stub().returns(Promise.resolve());
23
+ const clipboardFn = createClipboard({
24
+ story,
25
+ view,
26
+ writeText: writeTextStub,
27
+ });
28
+
29
+ await clipboardFn('copy');
30
+
31
+ t.calledWith(writeTextStub, ['selected text'], 'writeText should be called with selected text');
32
+ t.end();
33
+ });
34
+
35
+ test('clipboard: cut stores value in story', (t) => {
36
+ const story = createStoryStub();
37
+ const view = createViewStub('cut text');
38
+
39
+ const clipboardFn = createClipboard({
40
+ story,
41
+ view,
42
+ });
43
+
44
+ clipboardFn('cut');
45
+
46
+ t.ok(story.setData.called, 'story.setData should be called');
47
+ t.end();
48
+ });
49
+
50
+ test('clipboard: paste reads from clipboard and inserts', async (t) => {
51
+ const story = createStoryStub();
52
+ const view = createViewStub('');
53
+
54
+ const readTextStub = stub().returns(Promise.resolve('pasted content'));
55
+ const clipboardFn = createClipboard({
56
+ story,
57
+ view,
58
+ readText: readTextStub,
59
+ });
60
+
61
+ await clipboardFn('paste');
62
+
63
+ t.ok(view.dispatch.called, 'view.dispatch should be called');
64
+ t.end();
65
+ });
66
+
67
+ test('clipboard: paste falls back to story on clipboard error', async (t) => {
68
+ const story = createStoryStub();
69
+
70
+ story.getData = stub().returns('fallback text');
71
+
72
+ const view = createViewStub('');
73
+
74
+ const readTextStub = stub().returns(Promise.reject(Error('denied')));
75
+ const clipboardFn = createClipboard({
76
+ story,
77
+ view,
78
+ readText: readTextStub,
79
+ });
80
+
81
+ await clipboardFn('paste');
82
+
83
+ t.ok(view.dispatch.called, 'view.dispatch should be called');
84
+ t.end();
85
+ });
86
+
87
+ const createStoryStub = () => ({
88
+ setData: stub(),
89
+ getData: stub().returns(''),
90
+ });
91
+
92
+ const createViewStub = (selectedText = '') => ({
93
+ state: {
94
+ selection: {
95
+ main: {
96
+ from: 0,
97
+ to: selectedText.length,
98
+ },
99
+ },
100
+ sliceDoc: () => selectedText,
101
+ },
102
+ dispatch: stub().returns(true),
103
+ });
104
+
105
+ function createClipboard({story, view, writeText, readText}) {
106
+ const clipboard = {
107
+ writeText: writeText || stub().returns(Promise.resolve()),
108
+ readText: readText || stub().returns(Promise.resolve('')),
109
+ };
110
+
111
+ const showMessageOnce = stub();
112
+
113
+ return (cmd) => {
114
+ const NAME = 'editor-clipboard';
115
+
116
+ const value = view.state.sliceDoc(view.state.selection.main.from, view.state.selection.main.to);
117
+
118
+ const insert = (text) => {
119
+ view.dispatch({
120
+ changes: {
121
+ from: view.state.selection.main.from,
122
+ to: view.state.selection.main.to,
123
+ insert: text,
124
+ },
125
+ });
126
+ };
127
+
128
+ if (cmd === 'copy') {
129
+ story.setData(NAME, value);
130
+ return clipboard.writeText(value);
131
+ }
132
+
133
+ if (cmd === 'cut') {
134
+ story.setData(NAME, value);
135
+ return cut(view) ? Promise.resolve() : Promise.reject();
136
+ }
137
+
138
+ return clipboard
139
+ .readText()
140
+ .then(insert)
141
+ .catch(() => {
142
+ showMessageOnce('Could not paste from clipboard. Inner buffer used.');
143
+
144
+ const fallbackValue = story.getData(NAME);
145
+
146
+ insert(fallbackValue);
147
+ });
148
+ };
149
+ }
150
+
151
+ function cut(view) {
152
+ view.dispatch({
153
+ changes: {
154
+ from: view.state.selection.main.from,
155
+ to: view.state.selection.main.to,
156
+ insert: '',
157
+ },
158
+ });
159
+ return true;
160
+ }
@@ -0,0 +1,8 @@
1
+ import type {EditorView} from '@codemirror/view';
2
+ import type {SourceCode} from './types.js';
3
+
4
+ export function getValue(view: EditorView): SourceCode;
5
+ export const getDocValue: typeof getValue;
6
+
7
+ export function setValue(view: EditorView, value: SourceCode): void;
8
+ export const setDocValue: typeof setValue;
@@ -0,0 +1,14 @@
1
+ export const getValue = (view) => view.state.doc.toString();
2
+ export const getDocValue = getValue;
3
+
4
+ export function setValue(view, value) {
5
+ view.dispatch({
6
+ changes: {
7
+ from: 0,
8
+ to: view.state.doc.length,
9
+ insert: value,
10
+ },
11
+ });
12
+ }
13
+
14
+ export const setDocValue = setValue;
@@ -0,0 +1,69 @@
1
+ import {test} from 'supertape';
2
+ import {EditorState} from '@codemirror/state';
3
+ import {EditorView} from '@codemirror/view';
4
+ import {
5
+ getValue,
6
+ setValue,
7
+ getDocValue,
8
+ setDocValue,
9
+ } from './content.js';
10
+
11
+ function makeView(doc = 'hello') {
12
+ const element = document.createElement('div');
13
+ document.body.appendChild(element);
14
+
15
+ return new EditorView({
16
+ state: EditorState.create({
17
+ doc,
18
+ }),
19
+ parent: element,
20
+ });
21
+ }
22
+
23
+ test('content: getValue returns document string', (t) => {
24
+ const view = makeView('abc');
25
+ const result = getValue(view);
26
+ const expected = 'abc';
27
+
28
+ view.destroy();
29
+
30
+ t.equal(result, expected);
31
+ t.end();
32
+ });
33
+
34
+ test('content: setValue replaces content', (t) => {
35
+ const view = makeView('abc');
36
+ setValue(view, 'xyz');
37
+
38
+ const result = getValue(view);
39
+ const expected = 'xyz';
40
+
41
+ view.destroy();
42
+
43
+ t.equal(result, expected);
44
+ t.end();
45
+ });
46
+
47
+ test('content: getDocValue aliases getValue', (t) => {
48
+ const view = makeView('abc');
49
+ const result = getDocValue(view);
50
+ const expected = 'abc';
51
+
52
+ view.destroy();
53
+
54
+ t.equal(result, expected);
55
+ t.end();
56
+ });
57
+
58
+ test('content: setDocValue aliases setValue', (t) => {
59
+ const view = makeView('abc');
60
+ setDocValue(view, 'xyz');
61
+
62
+ const result = getValue(view);
63
+ const expected = 'xyz';
64
+
65
+ view.destroy();
66
+
67
+ t.equal(result, expected);
68
+ t.end();
69
+ });
@@ -0,0 +1,32 @@
1
+ import {Compartment} from '@codemirror/state';
2
+ import {type ViewUpdate, EditorView} from '@codemirror/view';
3
+ import type {
4
+ SourceCode,
5
+ KeyMap,
6
+ EditorMode,
7
+ EditorTheme,
8
+ } from './types.js';
9
+
10
+ export type CreateEditorOptions = {
11
+ value?: SourceCode;
12
+ mode?: EditorMode | {
13
+ name: EditorMode;
14
+ json?: boolean;
15
+ };
16
+ keyMap?: KeyMap;
17
+ theme?: EditorTheme;
18
+ lineNumbers?: boolean;
19
+ readOnly?: boolean;
20
+ foldGutter?: boolean;
21
+ updateListener?: (update: ViewUpdate) => void;
22
+ };
23
+
24
+ // Augmented EditorView with compartment refs for dynamic option changes.
25
+ export type QwordEditorView = EditorView & {
26
+ _themeCompartment: Compartment;
27
+ _keymapCompartment: Compartment;
28
+ _langCompartment: Compartment;
29
+ _historyCompartment: Compartment;
30
+ };
31
+
32
+ export function createEditor(element: Element, options?: CreateEditorOptions): QwordEditorView;
@@ -0,0 +1,95 @@
1
+ import {EditorState, Compartment} from '@codemirror/state';
2
+ import {
3
+ EditorView,
4
+ lineNumbers as lineNumbersExtension,
5
+ drawSelection,
6
+ keymap,
7
+ } from '@codemirror/view';
8
+ import {history, historyKeymap} from '@codemirror/commands';
9
+ import {
10
+ foldGutter,
11
+ codeFolding,
12
+ syntaxHighlighting,
13
+ } from '@codemirror/language';
14
+ import {highlightStyle} from './highlight.js';
15
+ import {
16
+ keymapExtension,
17
+ themeExtension,
18
+ languageExtension,
19
+ } from './options.js';
20
+ import {markField, lineField} from './decorations.js';
21
+
22
+ export function createEditor(element, options = {}) {
23
+ const {
24
+ value = '',
25
+ mode = 'javascript',
26
+ keyMap = 'default',
27
+ theme = 'default',
28
+ lineNumbers = true,
29
+ readOnly = false,
30
+ foldGutter: fold = false,
31
+ updateListener,
32
+ } = options;
33
+
34
+ const themeCompartment = new Compartment();
35
+ const keymapCompartment = new Compartment();
36
+ const langCompartment = new Compartment();
37
+ const historyCompartment = new Compartment();
38
+
39
+ const hideCursorOnBlur = EditorView.theme({
40
+ '&:not(.cm-focused) .cm-fat-cursor': {
41
+ display: 'none',
42
+ },
43
+ });
44
+
45
+ const extensions = [
46
+ historyCompartment.of([
47
+ history(),
48
+ keymap.of(historyKeymap),
49
+ ]),
50
+ markField,
51
+ lineField,
52
+ themeCompartment.of(themeExtension(theme)),
53
+ keymapCompartment.of(keymapExtension(keyMap)),
54
+ langCompartment.of(languageExtension(mode)),
55
+ syntaxHighlighting(highlightStyle),
56
+ ...lineNumbers
57
+ ? [
58
+ lineNumbersExtension(),
59
+ ]
60
+ : [],
61
+ ...fold
62
+ ? [
63
+ foldGutter(),
64
+ codeFolding(),
65
+ ]
66
+ : [],
67
+ ...readOnly
68
+ ? [
69
+ EditorState.readOnly.of(true),
70
+ ]
71
+ : [],
72
+ ...updateListener
73
+ ? [
74
+ EditorView.updateListener.of(updateListener),
75
+ ]
76
+ : [],
77
+ drawSelection(),
78
+ hideCursorOnBlur,
79
+ ];
80
+
81
+ const view = new EditorView({
82
+ state: EditorState.create({
83
+ doc: value,
84
+ extensions,
85
+ }),
86
+ parent: element,
87
+ });
88
+
89
+ view._themeCompartment = themeCompartment;
90
+ view._keymapCompartment = keymapCompartment;
91
+ view._langCompartment = langCompartment;
92
+ view._historyCompartment = historyCompartment;
93
+
94
+ return view;
95
+ }
@@ -0,0 +1,192 @@
1
+ import {test} from 'supertape';
2
+ import {createEditor} from './create.js';
3
+
4
+ function makeContainer() {
5
+ const element = document.createElement('div');
6
+ document.body.appendChild(element);
7
+
8
+ return element;
9
+ }
10
+
11
+ test('create: createEditor doc matches value option', (t) => {
12
+ const view = createEditor(makeContainer(), {
13
+ value: 'hello',
14
+ });
15
+
16
+ const result = view.state.doc.toString();
17
+ const expected = 'hello';
18
+
19
+ view.destroy();
20
+
21
+ t.equal(result, expected);
22
+ t.end();
23
+ });
24
+
25
+ test('create: createEditor readOnly true sets state readOnly', (t) => {
26
+ const view = createEditor(makeContainer(), {
27
+ readOnly: true,
28
+ });
29
+
30
+ view.destroy();
31
+
32
+ t.ok(view.state.readOnly);
33
+ t.end();
34
+ });
35
+
36
+ test('create: createEditor lineNumbers false does not throw', (t) => {
37
+ const view = createEditor(makeContainer(), {
38
+ lineNumbers: false,
39
+ });
40
+
41
+ view.destroy();
42
+
43
+ t.ok(view.state);
44
+ t.end();
45
+ });
46
+
47
+ test('create: createEditor foldGutter true does not throw', (t) => {
48
+ const view = createEditor(makeContainer(), {
49
+ foldGutter: true,
50
+ });
51
+
52
+ view.destroy();
53
+
54
+ t.ok(view.state);
55
+ t.end();
56
+ });
57
+
58
+ test('create: createEditor vim keyMap does not throw', (t) => {
59
+ const view = createEditor(makeContainer(), {
60
+ keyMap: 'vim',
61
+ });
62
+
63
+ view.destroy();
64
+
65
+ t.ok(view.state);
66
+ t.end();
67
+ });
68
+
69
+ test('create: createEditor emacs keyMap does not throw', (t) => {
70
+ const view = createEditor(makeContainer(), {
71
+ keyMap: 'emacs',
72
+ });
73
+
74
+ view.destroy();
75
+
76
+ t.ok(view.state);
77
+ t.end();
78
+ });
79
+
80
+ test('create: createEditor nord theme does not throw', (t) => {
81
+ const view = createEditor(makeContainer(), {
82
+ theme: 'nord',
83
+ });
84
+
85
+ view.destroy();
86
+
87
+ t.ok(view.state);
88
+ t.end();
89
+ });
90
+
91
+ test('create: createEditor json mode does not throw', (t) => {
92
+ const view = createEditor(makeContainer(), {
93
+ mode: 'json',
94
+ });
95
+
96
+ view.destroy();
97
+
98
+ t.ok(view.state);
99
+ t.end();
100
+ });
101
+
102
+ test('create: createEditor html mode does not throw', (t) => {
103
+ const view = createEditor(makeContainer(), {
104
+ mode: 'html',
105
+ });
106
+
107
+ view.destroy();
108
+
109
+ t.ok(view.state);
110
+ t.end();
111
+ });
112
+
113
+ test('create: createEditor mode object does not throw', (t) => {
114
+ const view = createEditor(makeContainer(), {
115
+ mode: {
116
+ name: 'javascript',
117
+ json: true,
118
+ },
119
+ });
120
+
121
+ view.destroy();
122
+
123
+ t.ok(view.state);
124
+ t.end();
125
+ });
126
+
127
+ test('create: createEditor updateListener is called on change', (t) => {
128
+ let called = false;
129
+ const view = createEditor(makeContainer(), {
130
+ updateListener: () => {
131
+ called = true;
132
+ },
133
+ });
134
+
135
+ view.dispatch({
136
+ changes: {
137
+ from: 0,
138
+ to: 0,
139
+ insert: 'x',
140
+ },
141
+ });
142
+ view.destroy();
143
+
144
+ t.ok(called);
145
+ t.end();
146
+ });
147
+
148
+ test('create: createEditor exposes themeCompartment on view', (t) => {
149
+ const view = createEditor(makeContainer());
150
+ view.destroy();
151
+
152
+ t.ok(view._themeCompartment);
153
+ t.end();
154
+ });
155
+
156
+ test('create: createEditor exposes keymapCompartment on view', (t) => {
157
+ const view = createEditor(makeContainer());
158
+ view.destroy();
159
+
160
+ t.ok(view._keymapCompartment);
161
+ t.end();
162
+ });
163
+
164
+ test('create: createEditor exposes langCompartment on view', (t) => {
165
+ const view = createEditor(makeContainer());
166
+ view.destroy();
167
+
168
+ t.ok(view._langCompartment);
169
+ t.end();
170
+ });
171
+
172
+ test('create: createEditor exposes historyCompartment on view', (t) => {
173
+ const view = createEditor(makeContainer());
174
+ view.destroy();
175
+
176
+ t.ok(view._historyCompartment);
177
+ t.end();
178
+ });
179
+
180
+ test('create: createEditor produces hl-keyword spans for javascript', (t) => {
181
+ const view = createEditor(makeContainer(), {
182
+ value: 'const x = 1;',
183
+ mode: 'javascript',
184
+ });
185
+
186
+ const spans = view.dom.querySelectorAll('span.hl-keyword');
187
+
188
+ view.destroy();
189
+
190
+ t.ok(spans.length > 0);
191
+ t.end();
192
+ });