@opensumi/playwright 2.21.13 → 2.22.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 (155) hide show
  1. package/lib/app.d.ts +2 -1
  2. package/lib/app.d.ts.map +1 -1
  3. package/lib/app.js +21 -9
  4. package/lib/app.js.map +1 -1
  5. package/lib/component-editor.d.ts +2 -2
  6. package/lib/debug-view.d.ts +10 -0
  7. package/lib/debug-view.d.ts.map +1 -1
  8. package/lib/debug-view.js +41 -1
  9. package/lib/debug-view.js.map +1 -1
  10. package/lib/diff-editor.d.ts +4 -0
  11. package/lib/diff-editor.d.ts.map +1 -0
  12. package/lib/diff-editor.js +8 -0
  13. package/lib/diff-editor.js.map +1 -0
  14. package/lib/editor.d.ts +6 -5
  15. package/lib/editor.d.ts.map +1 -1
  16. package/lib/editor.js +6 -2
  17. package/lib/editor.js.map +1 -1
  18. package/lib/explorer-view.d.ts +4 -0
  19. package/lib/explorer-view.d.ts.map +1 -1
  20. package/lib/explorer-view.js +36 -10
  21. package/lib/explorer-view.js.map +1 -1
  22. package/lib/filetree-view.d.ts +1 -1
  23. package/lib/filetree-view.js +2 -2
  24. package/lib/filetree-view.js.map +1 -1
  25. package/lib/index.d.ts +2 -1
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +2 -1
  28. package/lib/index.js.map +1 -1
  29. package/lib/menu.d.ts +1 -1
  30. package/lib/opened-editor-view.d.ts +2 -2
  31. package/lib/opened-editor-view.d.ts.map +1 -1
  32. package/lib/opened-editor-view.js +4 -4
  33. package/lib/opened-editor-view.js.map +1 -1
  34. package/lib/outline-view.d.ts +9 -0
  35. package/lib/outline-view.d.ts.map +1 -0
  36. package/lib/outline-view.js +38 -0
  37. package/lib/outline-view.js.map +1 -0
  38. package/lib/output-view.d.ts +10 -0
  39. package/lib/output-view.d.ts.map +1 -0
  40. package/lib/output-view.js +78 -0
  41. package/lib/output-view.js.map +1 -0
  42. package/lib/panel.js +2 -2
  43. package/lib/panel.js.map +1 -1
  44. package/lib/scm-view.d.ts +21 -0
  45. package/lib/scm-view.d.ts.map +1 -0
  46. package/lib/scm-view.js +65 -0
  47. package/lib/scm-view.js.map +1 -0
  48. package/lib/search-view.d.ts +34 -0
  49. package/lib/search-view.d.ts.map +1 -1
  50. package/lib/search-view.js +196 -4
  51. package/lib/search-view.js.map +1 -1
  52. package/lib/source-control-view.d.ts +8 -0
  53. package/lib/source-control-view.d.ts.map +1 -0
  54. package/lib/source-control-view.js +45 -0
  55. package/lib/source-control-view.js.map +1 -0
  56. package/lib/terminal-view.d.ts +10 -0
  57. package/lib/terminal-view.d.ts.map +1 -0
  58. package/lib/terminal-view.js +50 -0
  59. package/lib/terminal-view.js.map +1 -0
  60. package/lib/tests/debug.test.js +49 -0
  61. package/lib/tests/debug.test.js.map +1 -1
  62. package/lib/tests/editor/undoRedo.test.d.ts +2 -0
  63. package/lib/tests/editor/undoRedo.test.d.ts.map +1 -0
  64. package/lib/tests/editor/undoRedo.test.js +52 -0
  65. package/lib/tests/editor/undoRedo.test.js.map +1 -0
  66. package/lib/tests/editor.test.js +0 -2
  67. package/lib/tests/editor.test.js.map +1 -1
  68. package/lib/tests/explorer-view.test.js +129 -5
  69. package/lib/tests/explorer-view.test.js.map +1 -1
  70. package/lib/tests/output.test.d.ts +2 -0
  71. package/lib/tests/output.test.d.ts.map +1 -0
  72. package/lib/tests/output.test.js +76 -0
  73. package/lib/tests/output.test.js.map +1 -0
  74. package/lib/tests/scm.test.js +42 -4
  75. package/lib/tests/scm.test.js.map +1 -1
  76. package/lib/tests/search-view.test.js +191 -8
  77. package/lib/tests/search-view.test.js.map +1 -1
  78. package/lib/tests/settings.test.d.ts +2 -0
  79. package/lib/tests/settings.test.d.ts.map +1 -0
  80. package/lib/tests/settings.test.js +95 -0
  81. package/lib/tests/settings.test.js.map +1 -0
  82. package/lib/text-editor.d.ts +7 -3
  83. package/lib/text-editor.d.ts.map +1 -1
  84. package/lib/text-editor.js +15 -0
  85. package/lib/text-editor.js.map +1 -1
  86. package/lib/tree-node.d.ts +3 -1
  87. package/lib/tree-node.d.ts.map +1 -1
  88. package/lib/tree-node.js +4 -4
  89. package/lib/tree-node.js.map +1 -1
  90. package/lib/utils/key.d.ts +1 -0
  91. package/lib/utils/key.d.ts.map +1 -1
  92. package/lib/utils/key.js +6 -1
  93. package/lib/utils/key.js.map +1 -1
  94. package/lib/view.js +1 -1
  95. package/lib/view.js.map +1 -1
  96. package/package.json +12 -11
  97. package/src/app.ts +148 -0
  98. package/src/component-editor.ts +64 -0
  99. package/src/constans/index.ts +18 -0
  100. package/src/context-menu.ts +27 -0
  101. package/src/debug-view.ts +73 -0
  102. package/src/diff-editor.ts +3 -0
  103. package/src/editor.ts +135 -0
  104. package/src/explorer-view.ts +149 -0
  105. package/src/filetree-view.ts +28 -0
  106. package/src/index.ts +20 -0
  107. package/src/menu-item.ts +40 -0
  108. package/src/menu.ts +69 -0
  109. package/src/menubar.ts +53 -0
  110. package/src/opened-editor-view.ts +28 -0
  111. package/src/outline-view.ts +37 -0
  112. package/src/output-view.ts +76 -0
  113. package/src/panel.ts +50 -0
  114. package/src/quick-command-palette.ts +62 -0
  115. package/src/quick-open-palette.ts +62 -0
  116. package/src/scm-view.ts +72 -0
  117. package/src/search-view.ts +260 -0
  118. package/src/source-control-view.ts +44 -0
  119. package/src/terminal-view.ts +50 -0
  120. package/src/tests/app.test.ts +16 -0
  121. package/src/tests/debug.test.ts +121 -0
  122. package/src/tests/editor/undoRedo.test.ts +55 -0
  123. package/src/tests/editor.test.ts +141 -0
  124. package/src/tests/explorer-view.test.ts +329 -0
  125. package/src/tests/hooks/index.ts +13 -0
  126. package/src/tests/keymaps.test.ts +118 -0
  127. package/src/tests/language.test.ts +55 -0
  128. package/src/tests/output.test.ts +87 -0
  129. package/src/tests/scm.test.ts +84 -0
  130. package/src/tests/search-view.test.ts +239 -0
  131. package/src/tests/settings.test.ts +115 -0
  132. package/src/tests/workspaces/debug/.sumi/launch.json +15 -0
  133. package/src/tests/workspaces/debug/index.js +18 -0
  134. package/src/tests/workspaces/default/editor-undo-redo.text +0 -0
  135. package/src/tests/workspaces/default/editor.js +0 -0
  136. package/src/tests/workspaces/default/editor2.js +87 -0
  137. package/src/tests/workspaces/default/editor3.js +0 -0
  138. package/src/tests/workspaces/default/test/test.js +1 -0
  139. package/src/tests/workspaces/git-workspace/a.js +0 -0
  140. package/src/tests/workspaces/language/definition.ts +12 -0
  141. package/src/tests/workspaces/language/reference.ts +9 -0
  142. package/src/tests/workspaces/search/index.js +5 -0
  143. package/src/tests/workspaces/search/index2.js +1 -0
  144. package/src/text-editor.ts +333 -0
  145. package/src/tree-node.ts +98 -0
  146. package/src/utils/element.ts +35 -0
  147. package/src/utils/index.ts +2 -0
  148. package/src/utils/key.ts +11 -0
  149. package/src/view-base.ts +11 -0
  150. package/src/view.ts +90 -0
  151. package/src/workspace.ts +36 -0
  152. package/lib/terminal.d.ts +0 -7
  153. package/lib/terminal.d.ts.map +0 -1
  154. package/lib/terminal.js +0 -25
  155. package/lib/terminal.js.map +0 -1
@@ -0,0 +1,333 @@
1
+ import { ElementHandle, Page } from '@playwright/test';
2
+
3
+ import { OpenSumiApp } from './app';
4
+ import { OpenSumiContextMenu } from './context-menu';
5
+ import { OpenSumiEditor } from './editor';
6
+ import { OpenSumiTreeNode } from './tree-node';
7
+ import { keypressWithCmdCtrl, keypressWithCmdCtrlAndShift } from './utils';
8
+
9
+ abstract class ViewsModel {
10
+ constructor(readonly page: Page) {}
11
+ protected viewElement: ElementHandle<SVGElement | HTMLElement> | null;
12
+ async mount(v: ElementHandle<SVGElement | HTMLElement> | null): Promise<void> {
13
+ this.viewElement = v;
14
+ }
15
+ }
16
+
17
+ class GlyphMarginModel extends ViewsModel {
18
+ async getElement() {
19
+ const glyphMargin = await this.viewElement?.$('.glyph-margin');
20
+ const parent = await glyphMargin?.getProperty('parentNode');
21
+ return parent?.asElement();
22
+ }
23
+
24
+ async getOverlay(lineNumber: number) {
25
+ const margin = await this.getElement();
26
+ const overlays = await margin?.$$('.margin-view-overlays > div');
27
+ if (!overlays) {
28
+ return;
29
+ }
30
+
31
+ for (const node of overlays) {
32
+ const lineNode = await node.$('.line-numbers');
33
+ const content = await lineNode?.textContent();
34
+ if (content === lineNumber.toString()) {
35
+ return node;
36
+ }
37
+ }
38
+ }
39
+
40
+ async hasBreakpoint(node: ElementHandle<SVGElement | HTMLElement>): Promise<boolean> {
41
+ return !!(await node.$('.sumi-debug-breakpoint'));
42
+ }
43
+
44
+ async hasTopStackFrame(node: ElementHandle<SVGElement | HTMLElement>): Promise<boolean> {
45
+ return !!(await node.$('.sumi-debug-top-stack-frame'));
46
+ }
47
+
48
+ async hasTopStackFrameLine(node: ElementHandle<SVGElement | HTMLElement>): Promise<boolean> {
49
+ return !!(await node.$('.sumi-debug-top-stack-frame-line'));
50
+ }
51
+ }
52
+
53
+ class OverlaysModel extends ViewsModel {
54
+ async getElement() {
55
+ return await this.viewElement?.$('.view-overlays');
56
+ }
57
+
58
+ async getOverlay(lineNumber: number) {
59
+ const element = await this.getElement();
60
+ const overlay = await element?.$(`div:nth-child(${lineNumber})`);
61
+ return overlay;
62
+ }
63
+ }
64
+
65
+ export class OpenSumiTextEditor extends OpenSumiEditor {
66
+ private glyphMarginModel: GlyphMarginModel;
67
+ private overlaysModel: OverlaysModel;
68
+
69
+ constructor(app: OpenSumiApp, filestatElement: OpenSumiTreeNode) {
70
+ super(app, filestatElement);
71
+ this.glyphMarginModel = new GlyphMarginModel(this.page);
72
+ this.overlaysModel = new OverlaysModel(this.page);
73
+ }
74
+
75
+ async getGlyphMarginModel() {
76
+ const viewElement = await this.getViewElement();
77
+ this.glyphMarginModel.mount(viewElement);
78
+ return this.glyphMarginModel;
79
+ }
80
+
81
+ async getOverlaysModel() {
82
+ const viewElement = await this.getViewElement();
83
+ this.overlaysModel.mount(viewElement);
84
+ return this.overlaysModel;
85
+ }
86
+
87
+ async openLineContextMenuByLineNumber(lineNumber: number) {
88
+ const existingLine = await this.lineByLineNumber(lineNumber);
89
+ if (!existingLine) {
90
+ return;
91
+ }
92
+ return OpenSumiContextMenu.open(this.app, async () => existingLine);
93
+ }
94
+
95
+ async openGlyphMarginContextMenu() {
96
+ const glyphMargin = await this.getGlyphMarginModel();
97
+ const view = await glyphMargin.getElement();
98
+ if (!view) {
99
+ return;
100
+ }
101
+ return OpenSumiContextMenu.open(this.app, async () => view);
102
+ }
103
+
104
+ async openTabContextMenu() {
105
+ const view = await this.getTab();
106
+ if (!view) {
107
+ return;
108
+ }
109
+ return OpenSumiContextMenu.open(this.app, async () => view);
110
+ }
111
+
112
+ async numberOfLines(): Promise<number | undefined> {
113
+ await this.activate();
114
+ const viewElement = await this.getViewElement();
115
+ const lineElements = await viewElement?.$$('.view-lines .view-line');
116
+ return lineElements?.length;
117
+ }
118
+
119
+ async textContentOfLineByLineNumber(lineNumber: number): Promise<string | undefined> {
120
+ const lineElement = await this.lineByLineNumber(lineNumber);
121
+ const content = await lineElement?.textContent();
122
+ return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
123
+ }
124
+
125
+ async replaceLineWithLineNumber(text: string, lineNumber: number): Promise<void> {
126
+ await this.selectLineWithLineNumber(lineNumber);
127
+ await this.typeTextAndHitEnter(text);
128
+ }
129
+
130
+ protected async typeTextAndHitEnter(text: string): Promise<void> {
131
+ await this.page.keyboard.type(text);
132
+ await this.page.keyboard.press('Enter');
133
+ }
134
+
135
+ async typeText(text: string): Promise<void> {
136
+ await this.page.keyboard.type(text);
137
+ }
138
+ async saveByKeyboard(): Promise<void> {
139
+ await this.page.keyboard.press(keypressWithCmdCtrl('s'));
140
+ await this.waitForEditorDone();
141
+ }
142
+ async undoByKeyboard(): Promise<void> {
143
+ await this.page.keyboard.press(keypressWithCmdCtrl('z'));
144
+ await this.waitForEditorDone();
145
+ }
146
+ async redoByKeyboard(): Promise<void> {
147
+ await this.page.keyboard.press(keypressWithCmdCtrlAndShift('z'));
148
+ await this.waitForEditorDone();
149
+ }
150
+
151
+ async selectLineWithLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
152
+ await this.activate();
153
+ const lineElement = await this.lineByLineNumber(lineNumber);
154
+ await this.selectLine(lineElement);
155
+ return lineElement;
156
+ }
157
+
158
+ async placeCursorInLineWithLineNumber(
159
+ lineNumber: number,
160
+ ): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
161
+ await this.activate();
162
+ const lineElement = await this.lineByLineNumber(lineNumber);
163
+ await this.placeCursorInLine(lineElement);
164
+ return lineElement;
165
+ }
166
+
167
+ async placeCursorInLineWithPosition(
168
+ lineNumber: number,
169
+ columnNumber: number,
170
+ ): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
171
+ await this.activate();
172
+ const lineElement = await this.lineByLineNumber(lineNumber);
173
+ await this.placeCursorInLine(lineElement, 'start');
174
+ for (let i = 0; i < columnNumber; i++) {
175
+ await this.page.keyboard.press('ArrowRight', { delay: 200 });
176
+ }
177
+ return lineElement;
178
+ }
179
+
180
+ async deleteLineByLineNumber(lineNumber: number): Promise<void> {
181
+ await this.selectLineWithLineNumber(lineNumber);
182
+ await this.page.keyboard.press('Backspace');
183
+ }
184
+
185
+ async getGlyphMarginElement() {
186
+ await this.activate();
187
+ const viewElement = await this.getViewElement();
188
+ return await viewElement?.$('.glyph-margin');
189
+ }
190
+
191
+ async getCursorElement(): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
192
+ const viewElement = await this.getViewElement();
193
+
194
+ const cursorNode = await viewElement?.$('.cursor.monaco-mouse-cursor-text');
195
+ if (cursorNode) {
196
+ return cursorNode;
197
+ }
198
+ }
199
+
200
+ async getCursorLineNumber(node: ElementHandle<SVGElement | HTMLElement> | undefined) {
201
+ const style = await node!.getAttribute('style');
202
+ const tops = style?.match(/top: [0-9]*px;/g) || ['0'];
203
+ const topNums = tops[0].match(/\d+/g);
204
+ if (topNums && topNums.length > 0) {
205
+ let topNum: number | string = topNums[0];
206
+ topNum = Number(topNum);
207
+
208
+ // 每个 view-lines 默认高度都是 18
209
+ const line = topNum / 18 + 1;
210
+ return line;
211
+ }
212
+ return undefined;
213
+ }
214
+ async lineByLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
215
+ await this.activate();
216
+ const viewElement = await this.getViewElement();
217
+
218
+ const lineNode = await viewElement!.$(`.view-lines > div:nth-child(${lineNumber})`);
219
+
220
+ if (!lineNode) {
221
+ throw new Error(`Couldn't retrieve lines of text editor ${this.tabSelector}`);
222
+ }
223
+ return lineNode.asElement();
224
+ }
225
+
226
+ async textContentOfLineContainingText(text: string): Promise<string | undefined> {
227
+ await this.activate();
228
+ const lineElement = await this.lineContainingText(text);
229
+ const content = await lineElement?.textContent();
230
+ return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
231
+ }
232
+
233
+ async replaceLineContainingText(newText: string, oldText: string): Promise<void> {
234
+ await this.selectLineContainingText(oldText);
235
+ await this.typeTextAndHitEnter(newText);
236
+ }
237
+
238
+ async selectLineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
239
+ await this.activate();
240
+ const lineElement = await this.lineContainingText(text);
241
+ await this.selectLine(lineElement);
242
+ return lineElement;
243
+ }
244
+
245
+ async placeCursorInLineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
246
+ await this.activate();
247
+ const lineElement = await this.lineContainingText(text);
248
+ await this.placeCursorInLine(lineElement);
249
+ return lineElement;
250
+ }
251
+
252
+ async deleteLineContainingText(text: string): Promise<void> {
253
+ await this.selectLineContainingText(text);
254
+ await this.page.keyboard.press('Backspace');
255
+ }
256
+
257
+ async addTextToNewLineAfterLineContainingText(textContainedByExistingLine: string, newText: string): Promise<void> {
258
+ const existingLine = await this.lineContainingText(textContainedByExistingLine);
259
+ await this.placeCursorInLine(existingLine);
260
+ await this.page.keyboard.press('End');
261
+ await this.page.keyboard.press('Enter');
262
+ await this.page.keyboard.type(newText);
263
+ }
264
+
265
+ async addTextToNewLineAfterLineByLineNumber(lineNumber: number, newText: string): Promise<void> {
266
+ const existingLine = await this.lineByLineNumber(lineNumber);
267
+ await this.placeCursorInLine(existingLine);
268
+ await this.page.keyboard.press('End');
269
+ await this.page.keyboard.press('Enter');
270
+ await this.page.keyboard.type(newText);
271
+ }
272
+
273
+ async pasteContentAfterLineByLineNumber(lineNumber: number): Promise<void> {
274
+ const existingLine = await this.lineByLineNumber(lineNumber);
275
+ await this.placeCursorInLine(existingLine);
276
+ await this.page.keyboard.press('End');
277
+ await this.page.keyboard.press(keypressWithCmdCtrl('KeyV'));
278
+ }
279
+
280
+ protected async lineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
281
+ const viewElement = await this.getViewElement();
282
+ return viewElement?.waitForSelector(`.view-lines .view-line:has-text("${text}")`);
283
+ }
284
+
285
+ protected async selectLine(lineElement: ElementHandle<SVGElement | HTMLElement> | undefined): Promise<void> {
286
+ await lineElement?.click({ clickCount: 3 });
287
+ }
288
+
289
+ async placeCursorInLine(
290
+ lineElement: ElementHandle<SVGElement | HTMLElement> | undefined,
291
+ point: 'start' | 'end' = 'end',
292
+ ): Promise<void> {
293
+ if (!lineElement) {
294
+ return;
295
+ }
296
+
297
+ if (point === 'start') {
298
+ await lineElement.click({
299
+ position: { x: 0, y: 0 },
300
+ });
301
+ return;
302
+ }
303
+
304
+ await lineElement.click();
305
+ }
306
+
307
+ protected replaceEditorSymbolsWithSpace(content: string): string | Promise<string | undefined> {
308
+ // [ ] &nbsp; => \u00a0
309
+ // [·] &middot; => \u00b7
310
+ return content.replace(/[\u00a0\u00b7]/g, ' ');
311
+ }
312
+
313
+ protected async selectedSuggestion(): Promise<ElementHandle<SVGElement | HTMLElement>> {
314
+ return this.page.waitForSelector(this.viewSelector + ' .monaco-list-row.show-file-icons.focused');
315
+ }
316
+
317
+ async getSelectedSuggestionText(): Promise<string> {
318
+ const suggestion = await this.selectedSuggestion();
319
+ const text = await suggestion.textContent();
320
+ if (text === null) {
321
+ throw new Error('Text content could not be found');
322
+ }
323
+ return text;
324
+ }
325
+
326
+ async clearContent() {
327
+ const line = await this.lineByLineNumber(1);
328
+ await line?.click();
329
+ await this.placeCursorInLine(line);
330
+ await this.page.keyboard.press(keypressWithCmdCtrl('KeyA'));
331
+ await this.page.keyboard.press('Delete');
332
+ }
333
+ }
@@ -0,0 +1,98 @@
1
+ import { ElementHandle } from '@playwright/test';
2
+
3
+ import { OpenSumiApp } from './app';
4
+ import { OpenSumiContextMenu } from './context-menu';
5
+
6
+ export interface IOpenSumiTreeNodeSelector {
7
+ labelClass: string;
8
+ descriptionClass: string;
9
+ badgeClass: string;
10
+ toggleClass: string;
11
+ selectedClass: string;
12
+ focusedClass: string;
13
+ collapsedClass: string;
14
+ }
15
+
16
+ export abstract class OpenSumiTreeNode {
17
+ constructor(
18
+ protected elementHandle: ElementHandle<SVGElement | HTMLElement>,
19
+ protected app: OpenSumiApp,
20
+ private selector: IOpenSumiTreeNodeSelector = {
21
+ labelClass: "[class*='node_displayname__']",
22
+ descriptionClass: "[class*='node_description__']",
23
+ badgeClass: "[class*='node_status___']",
24
+ toggleClass: "[class*='expansion_toggle__']",
25
+ selectedClass: "[class*='mod_selected__']",
26
+ focusedClass: "[class*='mod_focused__']",
27
+ collapsedClass: "[class*='mod_collapsed__']",
28
+ },
29
+ ) {}
30
+
31
+ async parentElementHandle() {
32
+ const parent = await this.elementHandle.getProperty('parentNode');
33
+ return parent.asElement();
34
+ }
35
+
36
+ async label() {
37
+ const labelElement = await this.elementHandle.$(this.selector.labelClass);
38
+ if (!labelElement) {
39
+ throw new Error(`Cannot read label from ${this.selector.labelClass} of ${this.elementHandle}`);
40
+ }
41
+ return labelElement.textContent();
42
+ }
43
+
44
+ async description() {
45
+ const descriptionElement = await this.elementHandle.$(this.selector.descriptionClass);
46
+ if (!descriptionElement) {
47
+ throw new Error(`Cannot read description from ${this.selector.descriptionClass} of ${this.elementHandle}`);
48
+ }
49
+ return descriptionElement.textContent();
50
+ }
51
+
52
+ async badge() {
53
+ const badgeElement = await this.elementHandle.$(this.selector.badgeClass);
54
+ if (!badgeElement) {
55
+ throw new Error(`Cannot read description from ${this.selector.badgeClass} of ${this.elementHandle}`);
56
+ }
57
+ return badgeElement.textContent();
58
+ }
59
+
60
+ async isSelected() {
61
+ const id = await this.elementHandle.getAttribute('data-id');
62
+ const parent = await this.parentElementHandle();
63
+ return !!(await parent?.$(`[data-id='${id}']${this.selector.selectedClass}`));
64
+ }
65
+
66
+ async isCollapsed() {
67
+ return !!(await this.elementHandle.$(this.selector.collapsedClass));
68
+ }
69
+
70
+ async isExpanded() {
71
+ return !(await this.elementHandle.$(this.selector.collapsedClass));
72
+ }
73
+
74
+ async expand() {
75
+ if (await this.isExpanded()) {
76
+ return;
77
+ }
78
+ const toggle = await this.elementHandle.waitForSelector(this.selector.toggleClass);
79
+ await toggle.click();
80
+ await this.elementHandle.waitForSelector(`${this.selector.toggleClass}:not(${this.selector.collapsedClass})`);
81
+ }
82
+
83
+ async collapse() {
84
+ if (await this.isCollapsed()) {
85
+ return;
86
+ }
87
+ const toggle = await this.elementHandle.waitForSelector(this.selector.toggleClass);
88
+ await toggle.click();
89
+ await this.elementHandle.waitForSelector(`${this.selector.collapsedClass}`);
90
+ }
91
+
92
+ async openContextMenu() {
93
+ return OpenSumiContextMenu.open(this.app, () => this.elementHandle.waitForSelector(this.selector.labelClass));
94
+ }
95
+
96
+ abstract getFsPath(): Promise<string | null>;
97
+ abstract open(preview?: boolean): Promise<void>;
98
+ }
@@ -0,0 +1,35 @@
1
+ import { ElementHandle } from '@playwright/test';
2
+
3
+ export async function isElementVisible(elementPromise: Promise<ElementHandle<SVGElement | HTMLElement> | null>) {
4
+ const element = await elementPromise;
5
+ return element ? element.isVisible() : false;
6
+ }
7
+
8
+ export async function containsClass(
9
+ elementPromise: Promise<ElementHandle<SVGElement | HTMLElement> | null> | undefined,
10
+ cssClass: string,
11
+ ) {
12
+ return elementContainsClass(await elementPromise, cssClass);
13
+ }
14
+
15
+ export async function elementContainsClass(
16
+ element: ElementHandle<SVGElement | HTMLElement> | null | undefined,
17
+ cssClass: string,
18
+ ) {
19
+ if (element) {
20
+ const classValue = await element.getAttribute('class');
21
+ if (classValue) {
22
+ return classValue?.split(' ').includes(cssClass);
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+
28
+ export async function textContent(elementPromise: Promise<ElementHandle<SVGElement | HTMLElement> | null>) {
29
+ const element = await elementPromise;
30
+ if (!element) {
31
+ return undefined;
32
+ }
33
+ const content = await element.textContent();
34
+ return content ? content : undefined;
35
+ }
@@ -0,0 +1,2 @@
1
+ export * from './element';
2
+ export * from './key';
@@ -0,0 +1,11 @@
1
+ import { isMacintosh, isWindows } from '@opensumi/ide-utils';
2
+
3
+ export const keypressWithCmdCtrl = (key: string) => {
4
+ const modifier = isMacintosh ? 'Meta' : isWindows ? 'Ctrl' : 'Control';
5
+ return `${modifier}+${key}`;
6
+ };
7
+
8
+ export const keypressWithCmdCtrlAndShift = (key: string) => {
9
+ const modifier = isMacintosh ? 'Meta' : isWindows ? 'Ctrl' : 'Control';
10
+ return `${modifier}+Shift+${key}`;
11
+ };
@@ -0,0 +1,11 @@
1
+ import { ElementHandle, Page } from '@playwright/test';
2
+
3
+ import { OpenSumiApp } from './app';
4
+
5
+ export abstract class OpenSumiViewBase {
6
+ constructor(public app: OpenSumiApp) {}
7
+
8
+ get page(): Page {
9
+ return this.app.page;
10
+ }
11
+ }
package/src/view.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { ElementHandle } from '@playwright/test';
2
+
3
+ import { OpenSumiApp } from './app';
4
+ import { isElementVisible, containsClass } from './utils';
5
+ import { OpenSumiViewBase } from './view-base';
6
+
7
+ export interface OpenSumiViewInfo {
8
+ tabSelector: string;
9
+ viewSelector: string;
10
+ name?: string;
11
+ }
12
+
13
+ export class OpenSumiView extends OpenSumiViewBase {
14
+ constructor(app: OpenSumiApp, private readonly data: OpenSumiViewInfo) {
15
+ super(app);
16
+ }
17
+
18
+ get tabSelector() {
19
+ return this.data.tabSelector;
20
+ }
21
+
22
+ get viewSelector() {
23
+ return this.data.viewSelector;
24
+ }
25
+
26
+ get name() {
27
+ return this.data.name;
28
+ }
29
+
30
+ getViewElement(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
31
+ return this.page.$(this.viewSelector);
32
+ }
33
+
34
+ getTabElement(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
35
+ return this.page.$(this.tabSelector);
36
+ }
37
+
38
+ async open(): Promise<OpenSumiView | undefined> {
39
+ if (!this.name) {
40
+ return;
41
+ }
42
+ await this.app.quickOpenPalette.type('view ');
43
+ await this.app.quickOpenPalette.trigger(this.name.toUpperCase());
44
+ await this.waitForVisible();
45
+ return this;
46
+ }
47
+
48
+ async focus(): Promise<void> {
49
+ await this.activate();
50
+ const view = await this.getViewElement();
51
+ await view?.click();
52
+ }
53
+
54
+ async activate(): Promise<void> {
55
+ await this.page.waitForSelector(this.tabSelector, { state: 'visible' });
56
+ if (!(await this.isActive())) {
57
+ const tab = await this.getTabElement();
58
+ await tab?.click();
59
+ }
60
+ return this.waitForVisible();
61
+ }
62
+
63
+ async waitForVisible(): Promise<void> {
64
+ await this.page.waitForSelector(this.viewSelector, { state: 'visible' });
65
+ }
66
+
67
+ async isTabVisible(): Promise<boolean> {
68
+ return isElementVisible(this.getTabElement());
69
+ }
70
+
71
+ async isDisplayed(): Promise<boolean> {
72
+ return isElementVisible(this.getViewElement());
73
+ }
74
+
75
+ async isActive(): Promise<boolean> {
76
+ return (await this.isTabVisible()) && containsClass(this.getTabElement(), 'p-mod-current');
77
+ }
78
+
79
+ async isClosable(): Promise<boolean> {
80
+ return (await this.isTabVisible()) && containsClass(this.getTabElement(), 'p-mod-closable');
81
+ }
82
+
83
+ async isVisible() {
84
+ return this.isTabVisible();
85
+ }
86
+
87
+ protected async waitUntilClosed(): Promise<void> {
88
+ await this.page.waitForSelector(this.tabSelector, { state: 'detached' });
89
+ }
90
+ }
@@ -0,0 +1,36 @@
1
+ import path from 'path';
2
+
3
+ import fse from 'fs-extra';
4
+ import temp from 'temp';
5
+
6
+ import { Disposable, URI } from '@opensumi/ide-utils';
7
+
8
+ export class OpenSumiWorkspace extends Disposable {
9
+ private workspacePath: string;
10
+
11
+ constructor(private filesToWorkspace: string[]) {
12
+ super();
13
+ const track = temp.track();
14
+ this.disposables.push({
15
+ dispose: () => {
16
+ track.cleanupSync();
17
+ },
18
+ });
19
+ this.workspacePath = fse.realpathSync(path.join(temp.mkdirSync('workspace')));
20
+ }
21
+
22
+ get workspace() {
23
+ return new URI(this.workspacePath);
24
+ }
25
+
26
+ async initWorksapce() {
27
+ if (!fse.existsSync(this.workspacePath)) {
28
+ await fse.ensureDir(this.workspacePath);
29
+ }
30
+ for (const file of this.filesToWorkspace) {
31
+ if (fse.existsSync(file)) {
32
+ await fse.copy(file, this.workspacePath);
33
+ }
34
+ }
35
+ }
36
+ }
package/lib/terminal.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import { OpenSumiApp } from './app';
2
- import { OpenSumiPanel } from './panel';
3
- export declare class OpenSumiTerminal extends OpenSumiPanel {
4
- constructor(app: OpenSumiApp);
5
- sendText(text: string): Promise<void>;
6
- }
7
- //# sourceMappingURL=terminal.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,qBAAa,gBAAiB,SAAQ,aAAa;gBACrC,GAAG,EAAE,WAAW;IAItB,QAAQ,CAAC,IAAI,EAAE,MAAM;CAa5B"}
package/lib/terminal.js DELETED
@@ -1,25 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpenSumiTerminal = void 0;
4
- const panel_1 = require("./panel");
5
- class OpenSumiTerminal extends panel_1.OpenSumiPanel {
6
- constructor(app) {
7
- super(app, 'terminal');
8
- }
9
- async sendText(text) {
10
- var _a;
11
- const visible = await this.isVisible();
12
- if (!visible) {
13
- await this.open();
14
- }
15
- await this.focus();
16
- const box = await ((_a = this.view) === null || _a === void 0 ? void 0 : _a.boundingBox());
17
- if (box) {
18
- await this.app.page.mouse.click(box.x + (box === null || box === void 0 ? void 0 : box.width) / 2, box.y + (box === null || box === void 0 ? void 0 : box.height) / 2);
19
- }
20
- await this.page.keyboard.type(text);
21
- await this.app.page.keyboard.press('Enter');
22
- }
23
- }
24
- exports.OpenSumiTerminal = OpenSumiTerminal;
25
- //# sourceMappingURL=terminal.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":";;;AACA,mCAAwC;AAExC,MAAa,gBAAiB,SAAQ,qBAAa;IACjD,YAAY,GAAgB;QAC1B,KAAK,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;;QACzB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACvC,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;SACnB;QACD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,MAAM,CAAA,MAAA,IAAI,CAAC,IAAI,0CAAE,WAAW,EAAE,CAAA,CAAC;QAC3C,IAAI,GAAG,EAAE;YACP,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,KAAK,IAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,MAAM,IAAG,CAAC,CAAC,CAAC;SAClF;QACD,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;CACF;AAlBD,4CAkBC"}