@theia/playwright 1.37.0-next.1 → 1.37.0-next.11

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 (92) hide show
  1. package/lib/index.d.ts +4 -0
  2. package/lib/index.d.ts.map +1 -1
  3. package/lib/index.js +4 -0
  4. package/lib/index.js.map +1 -1
  5. package/lib/tests/theia-explorer-view.test.js +25 -5
  6. package/lib/tests/theia-explorer-view.test.js.map +1 -1
  7. package/lib/tests/theia-main-menu.test.js +2 -2
  8. package/lib/tests/theia-main-menu.test.js.map +1 -1
  9. package/lib/tests/theia-output-view.test.d.ts +2 -0
  10. package/lib/tests/theia-output-view.test.d.ts.map +1 -0
  11. package/lib/tests/theia-output-view.test.js +77 -0
  12. package/lib/tests/theia-output-view.test.js.map +1 -0
  13. package/lib/tests/theia-quick-command.test.js +14 -0
  14. package/lib/tests/theia-quick-command.test.js.map +1 -1
  15. package/lib/tests/theia-sample-app.test.js +3 -30
  16. package/lib/tests/theia-sample-app.test.js.map +1 -1
  17. package/lib/tests/theia-terminal-view.test.d.ts +2 -0
  18. package/lib/tests/theia-terminal-view.test.d.ts.map +1 -0
  19. package/lib/tests/theia-terminal-view.test.js +76 -0
  20. package/lib/tests/theia-terminal-view.test.js.map +1 -0
  21. package/lib/tests/theia-toolbar.test.d.ts +2 -0
  22. package/lib/tests/theia-toolbar.test.d.ts.map +1 -0
  23. package/lib/tests/theia-toolbar.test.js +60 -0
  24. package/lib/tests/theia-toolbar.test.js.map +1 -0
  25. package/lib/tests/theia-workspace.test.js +5 -3
  26. package/lib/tests/theia-workspace.test.js.map +1 -1
  27. package/lib/theia-app.d.ts +7 -0
  28. package/lib/theia-app.d.ts.map +1 -1
  29. package/lib/theia-app.js +31 -1
  30. package/lib/theia-app.js.map +1 -1
  31. package/lib/theia-explorer-view.d.ts +11 -4
  32. package/lib/theia-explorer-view.d.ts.map +1 -1
  33. package/lib/theia-explorer-view.js +54 -6
  34. package/lib/theia-explorer-view.js.map +1 -1
  35. package/lib/theia-monaco-editor.d.ts +16 -0
  36. package/lib/theia-monaco-editor.d.ts.map +1 -0
  37. package/lib/theia-monaco-editor.js +76 -0
  38. package/lib/theia-monaco-editor.js.map +1 -0
  39. package/lib/theia-output-channel.d.ts +25 -0
  40. package/lib/theia-output-channel.d.ts.map +1 -0
  41. package/lib/theia-output-channel.js +72 -0
  42. package/lib/theia-output-channel.js.map +1 -0
  43. package/lib/theia-output-view.d.ts +10 -0
  44. package/lib/theia-output-view.d.ts.map +1 -0
  45. package/lib/theia-output-view.js +82 -0
  46. package/lib/theia-output-view.js.map +1 -0
  47. package/lib/theia-quick-command-palette.d.ts +2 -1
  48. package/lib/theia-quick-command-palette.d.ts.map +1 -1
  49. package/lib/theia-quick-command-palette.js +9 -2
  50. package/lib/theia-quick-command-palette.js.map +1 -1
  51. package/lib/theia-terminal.d.ts +14 -0
  52. package/lib/theia-terminal.d.ts.map +1 -0
  53. package/lib/theia-terminal.js +60 -0
  54. package/lib/theia-terminal.js.map +1 -0
  55. package/lib/theia-text-editor.d.ts +2 -3
  56. package/lib/theia-text-editor.d.ts.map +1 -1
  57. package/lib/theia-text-editor.js +11 -40
  58. package/lib/theia-text-editor.js.map +1 -1
  59. package/lib/theia-toolbar-item.d.ts +11 -0
  60. package/lib/theia-toolbar-item.d.ts.map +1 -0
  61. package/lib/theia-toolbar-item.js +40 -0
  62. package/lib/theia-toolbar-item.js.map +1 -0
  63. package/lib/theia-toolbar.d.ts +20 -0
  64. package/lib/theia-toolbar.d.ts.map +1 -0
  65. package/lib/theia-toolbar.js +91 -0
  66. package/lib/theia-toolbar.js.map +1 -0
  67. package/lib/theia-tree-node.d.ts +1 -0
  68. package/lib/theia-tree-node.d.ts.map +1 -1
  69. package/lib/theia-tree-node.js +8 -0
  70. package/lib/theia-tree-node.js.map +1 -1
  71. package/package.json +13 -10
  72. package/src/index.ts +4 -0
  73. package/src/tests/resources/sample-files1/sampleFolderCompact/nestedFolder1/nestedFolder2/sampleFile1-1.txt +0 -0
  74. package/src/tests/theia-explorer-view.test.ts +26 -6
  75. package/src/tests/theia-main-menu.test.ts +2 -2
  76. package/src/tests/theia-output-view.test.ts +85 -0
  77. package/src/tests/theia-quick-command.test.ts +15 -0
  78. package/src/tests/theia-sample-app.test.ts +3 -34
  79. package/src/tests/theia-terminal-view.test.ts +85 -0
  80. package/src/tests/theia-toolbar.test.ts +65 -0
  81. package/src/tests/theia-workspace.test.ts +5 -3
  82. package/src/theia-app.ts +38 -1
  83. package/src/theia-explorer-view.ts +60 -7
  84. package/src/theia-monaco-editor.ts +83 -0
  85. package/src/theia-output-channel.ts +88 -0
  86. package/src/theia-output-view.ts +87 -0
  87. package/src/theia-quick-command-palette.ts +10 -2
  88. package/src/theia-terminal.ts +69 -0
  89. package/src/theia-text-editor.ts +13 -44
  90. package/src/theia-toolbar-item.ts +41 -0
  91. package/src/theia-toolbar.ts +99 -0
  92. package/src/theia-tree-node.ts +10 -1
@@ -112,11 +112,11 @@ export class TheiaExplorerView extends TheiaView {
112
112
  return file;
113
113
  }
114
114
 
115
- async fileStatNode(filePath: string): Promise<TheiaExplorerFileStatNode | undefined> {
116
- return this.fileStatNodeBySegments(...filePath.split('/'));
115
+ async fileStatNode(filePath: string, compact = false): Promise<TheiaExplorerFileStatNode | undefined> {
116
+ return compact ? this.compactFileStatNode(filePath) : this.fileStatNodeBySegments(...filePath.split('/'));
117
117
  }
118
118
 
119
- async fileStatNodeBySegments(...pathFragments: string[]): Promise<TheiaExplorerFileStatNode | undefined> {
119
+ protected async fileStatNodeBySegments(...pathFragments: string[]): Promise<TheiaExplorerFileStatNode | undefined> {
120
120
  await super.activate();
121
121
  const viewElement = await this.viewElement();
122
122
 
@@ -141,6 +141,35 @@ export class TheiaExplorerView extends TheiaView {
141
141
  return currentTreeNode;
142
142
  }
143
143
 
144
+ protected async compactFileStatNode(path: string): Promise<TheiaExplorerFileStatNode | undefined> {
145
+ // default setting `explorer.compactFolders=true` renders folders in a compact form - single child folders will be compressed in a combined tree element
146
+ await super.activate();
147
+ const viewElement = await this.viewElement();
148
+
149
+ // check if first segment folder needs to be expanded first (if folder has never been expanded, it will not show the compact folder structure)
150
+ await this.waitForVisibleFileNodes();
151
+ const firstSegment = path.split('/')[0];
152
+ const selector = this.treeNodeSelector(firstSegment);
153
+ const folderElement = await viewElement?.$(selector);
154
+ if (folderElement && await folderElement.isVisible()) {
155
+ const folderNode = await viewElement?.waitForSelector(selector, { state: 'visible' });
156
+ if (!folderNode) {
157
+ throw new Error(`Tree node '${selector}' not found in explorer`);
158
+ }
159
+ const folderFileStatNode = new TheiaExplorerFileStatNode(folderNode, this);
160
+ if (await folderFileStatNode.isCollapsed()) {
161
+ await folderFileStatNode.expand();
162
+ }
163
+ }
164
+ // now get tree node via the full path
165
+ const fullPathSelector = this.treeNodeSelector(path);
166
+ const treeNode = await viewElement?.waitForSelector(fullPathSelector, { state: 'visible' });
167
+ if (!treeNode) {
168
+ throw new Error(`Tree node '${fullPathSelector}' not found in explorer`);
169
+ }
170
+ return new TheiaExplorerFileStatNode(treeNode, this);
171
+ }
172
+
144
173
  async selectTreeNode(filePath: string): Promise<void> {
145
174
  await this.activate();
146
175
  const treeNode = await this.page.waitForSelector(this.treeNodeSelector(filePath));
@@ -148,6 +177,10 @@ export class TheiaExplorerView extends TheiaView {
148
177
  await treeNode.focus();
149
178
  } else {
150
179
  await treeNode.click({ modifiers: ['Control'] });
180
+ // make sure the click has been acted-upon before returning
181
+ while (!await this.isTreeNodeSelected(filePath)) {
182
+ console.debug('Waiting for clicked tree node to be selected: ' + filePath);
183
+ }
151
184
  }
152
185
  }
153
186
 
@@ -177,8 +210,8 @@ export class TheiaExplorerView extends TheiaView {
177
210
  await menuItem.click();
178
211
  }
179
212
 
180
- protected async existsNode(path: string, isDirectory: boolean): Promise<boolean> {
181
- const fileStatNode = await this.fileStatNode(path);
213
+ protected async existsNode(path: string, isDirectory: boolean, compact = false): Promise<boolean> {
214
+ const fileStatNode = await this.fileStatNode(path, compact);
182
215
  if (!fileStatNode) {
183
216
  return false;
184
217
  }
@@ -198,8 +231,14 @@ export class TheiaExplorerView extends TheiaView {
198
231
  return this.existsNode(path, false);
199
232
  }
200
233
 
201
- async existsDirectoryNode(path: string): Promise<boolean> {
202
- return this.existsNode(path, true);
234
+ async existsDirectoryNode(path: string, compact = false): Promise<boolean> {
235
+ return this.existsNode(path, true, compact);
236
+ }
237
+
238
+ async waitForTreeNodeVisible(path: string): Promise<void> {
239
+ // wait for tree node to be visible, e.g. after triggering create
240
+ const viewElement = await this.viewElement();
241
+ await viewElement?.waitForSelector(this.treeNodeSelector(path), { state: 'visible' });
203
242
  }
204
243
 
205
244
  async getNumberOfVisibleNodes(): Promise<number> {
@@ -231,4 +270,18 @@ export class TheiaExplorerView extends TheiaView {
231
270
  await this.refresh();
232
271
  }
233
272
 
273
+ override async waitForVisible(): Promise<void> {
274
+ await super.waitForVisible();
275
+ await this.page.waitForSelector(this.tabSelector, { state: 'visible' });
276
+ }
277
+
278
+ /**
279
+ * Waits until some non-dot file nodes are visible
280
+ */
281
+ async waitForVisibleFileNodes(): Promise<void> {
282
+ while ((await this.visibleFileStatNodes(DOT_FILES_FILTER)).length === 0) {
283
+ console.debug('Awaiting for tree nodes to appear');
284
+ }
285
+ }
286
+
234
287
  }
@@ -0,0 +1,83 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ElementHandle } from '@playwright/test';
18
+ import { TheiaPageObject } from './theia-page-object';
19
+ import { TheiaApp } from './theia-app';
20
+
21
+ export class TheiaMonacoEditor extends TheiaPageObject {
22
+ constructor(protected readonly selector: string, app: TheiaApp) {
23
+ super(app);
24
+ }
25
+
26
+ async waitForVisible(): Promise<void> {
27
+ await this.page.waitForSelector(this.selector, { state: 'visible' });
28
+ }
29
+
30
+ protected viewElement(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
31
+ return this.page.$(this.selector);
32
+ }
33
+
34
+ async numberOfLines(): Promise<number | undefined> {
35
+ await this.waitForVisible();
36
+ const viewElement = await this.viewElement();
37
+ const lineElements = await viewElement?.$$('.view-lines .view-line');
38
+ return lineElements?.length;
39
+ }
40
+
41
+ async textContentOfLineByLineNumber(lineNumber: number): Promise<string | undefined> {
42
+ await this.waitForVisible();
43
+ const lineElement = await this.lineByLineNumber(lineNumber);
44
+ const content = await lineElement?.textContent();
45
+ return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
46
+ }
47
+
48
+ async lineByLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
49
+ await this.waitForVisible();
50
+ const viewElement = await this.viewElement();
51
+ const lines = await viewElement?.$$('.view-lines > .view-line');
52
+ if (!lines) {
53
+ throw new Error('Couldn\'t retrieve lines of monaco editor');
54
+ }
55
+
56
+ const linesWithXCoordinates = [];
57
+ for (const lineElement of lines) {
58
+ const box = await lineElement.boundingBox();
59
+ linesWithXCoordinates.push({ x: box ? box.x : Number.MAX_VALUE, lineElement });
60
+ }
61
+ linesWithXCoordinates.sort((a, b) => a.x.toString().localeCompare(b.x.toString()));
62
+ return linesWithXCoordinates[lineNumber - 1].lineElement;
63
+ }
64
+
65
+ async textContentOfLineContainingText(text: string): Promise<string | undefined> {
66
+ await this.waitForVisible();
67
+ const lineElement = await this.lineContainingText(text);
68
+ const content = await lineElement?.textContent();
69
+ return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
70
+ }
71
+
72
+ async lineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
73
+ const viewElement = await this.viewElement();
74
+ return viewElement?.waitForSelector(`.view-lines .view-line:has-text("${text}")`);
75
+ }
76
+
77
+ protected replaceEditorSymbolsWithSpace(content: string): string | Promise<string | undefined> {
78
+ // [ ] &nbsp; => \u00a0 -- NO-BREAK SPACE
79
+ // [·] &middot; => \u00b7 -- MIDDLE DOT
80
+ // [] &zwnj; => \u200c -- ZERO WIDTH NON-JOINER
81
+ return content.replace(/[\u00a0\u00b7]/g, ' ').replace(/[\u200c]/g, '');
82
+ }
83
+ }
@@ -0,0 +1,88 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ElementHandle } from '@playwright/test';
18
+ import { TheiaOutputView } from './theia-output-view';
19
+ import { TheiaPageObject } from './theia-page-object';
20
+ import { isElementVisible } from './util';
21
+ import { TheiaMonacoEditor } from './theia-monaco-editor';
22
+
23
+ export interface TheiaOutputViewChannelData {
24
+ viewSelector: string;
25
+ dataUri: string;
26
+ channelName: string;
27
+ }
28
+
29
+ export class TheiaOutputViewChannel extends TheiaPageObject {
30
+
31
+ protected monacoEditor: TheiaMonacoEditor;
32
+
33
+ constructor(protected readonly data: TheiaOutputViewChannelData, protected readonly outputView: TheiaOutputView) {
34
+ super(outputView.app);
35
+ this.monacoEditor = new TheiaMonacoEditor(this.viewSelector, outputView.app);
36
+ }
37
+
38
+ protected get viewSelector(): string {
39
+ return this.data.viewSelector;
40
+ }
41
+
42
+ protected get dataUri(): string | undefined {
43
+ return this.data.dataUri;
44
+ }
45
+
46
+ protected get channelName(): string | undefined {
47
+ return this.data.channelName;
48
+ }
49
+
50
+ async waitForVisible(): Promise<void> {
51
+ await this.page.waitForSelector(this.viewSelector, { state: 'visible' });
52
+ }
53
+
54
+ async isDisplayed(): Promise<boolean> {
55
+ return isElementVisible(this.viewElement());
56
+ }
57
+
58
+ protected viewElement(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
59
+ return this.page.$(this.viewSelector);
60
+ }
61
+
62
+ async numberOfLines(): Promise<number | undefined> {
63
+ await this.waitForVisible();
64
+ return this.monacoEditor.numberOfLines();
65
+ }
66
+
67
+ async maxSeverityOfLineByLineNumber(lineNumber: number): Promise<'error' | 'warning' | 'info'> {
68
+ await this.waitForVisible();
69
+ const lineElement = await this.monacoEditor.lineByLineNumber(lineNumber);
70
+ const contents = await lineElement?.$$('span > span.mtk1');
71
+ if (!contents || contents.length < 1) {
72
+ throw new Error(`Could not find contents of line number ${lineNumber}!`);
73
+ }
74
+ const severityClassNames = await Promise.all(contents.map(
75
+ async content => (await content.getAttribute('class'))?.split(' ')[1]));
76
+
77
+ if (severityClassNames.includes('theia-output-error')) {
78
+ return 'error';
79
+ } else if (severityClassNames.includes('theia-output-warning')) {
80
+ return 'warning';
81
+ }
82
+ return 'info';
83
+ }
84
+
85
+ async textContentOfLineByLineNumber(lineNumber: number): Promise<string | undefined> {
86
+ return this.monacoEditor.textContentOfLineByLineNumber(lineNumber);
87
+ }
88
+ }
@@ -0,0 +1,87 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { TheiaApp } from './theia-app';
18
+ import { TheiaOutputViewChannel } from './theia-output-channel';
19
+ import { TheiaView } from './theia-view';
20
+ import { normalizeId } from './util';
21
+
22
+ const TheiaOutputViewData = {
23
+ tabSelector: '#shell-tab-outputView',
24
+ viewSelector: '#outputView',
25
+ viewName: 'Output'
26
+ };
27
+
28
+ export class TheiaOutputView extends TheiaView {
29
+ constructor(app: TheiaApp) {
30
+ super(TheiaOutputViewData, app);
31
+ }
32
+
33
+ async isOutputChannelSelected(outputChannelName: string): Promise<boolean> {
34
+ await this.activate();
35
+ const contentPanel = await this.page.$('#theia-bottom-content-panel');
36
+ if (contentPanel && (await contentPanel.isVisible())) {
37
+ const channelList = await contentPanel.$('#outputChannelList');
38
+ const selectedChannel = await channelList?.$('div.theia-select-component-label');
39
+ if (selectedChannel && (await selectedChannel.textContent()) === outputChannelName) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+
46
+ async getOutputChannel(outputChannelName: string): Promise<TheiaOutputViewChannel | undefined> {
47
+ await this.activate();
48
+ const channel = new TheiaOutputViewChannel(
49
+ {
50
+ viewSelector: 'div.p-Widget.theia-editor.p-DockPanel-widget > div.monaco-editor',
51
+ dataUri: normalizeId(`output:/${encodeURIComponent(outputChannelName)}`),
52
+ channelName: outputChannelName
53
+ },
54
+ this
55
+ );
56
+ await channel.waitForVisible();
57
+ if (await channel.isDisplayed()) {
58
+ return channel;
59
+ }
60
+ return undefined;
61
+ }
62
+
63
+ async selectOutputChannel(outputChannelName: string): Promise<boolean> {
64
+ await this.activate();
65
+ const contentPanel = await this.page.$('#theia-bottom-content-panel');
66
+ if (contentPanel && (await contentPanel.isVisible())) {
67
+ const channelSelectComponent = await contentPanel.$('#outputChannelList');
68
+ if (!channelSelectComponent) {
69
+ throw Error('Output Channel List not visible.');
70
+ }
71
+ // open output channel list component
72
+ await channelSelectComponent.click();
73
+ const channelContainer = await this.page.waitForSelector('#select-component-container > div.theia-select-component-dropdown');
74
+ if (!channelContainer) {
75
+ throw Error('Output Channel List could not be opened.');
76
+ }
77
+ const channels = await channelContainer.$$('div.theia-select-component-option-value');
78
+ for (const channel of channels) {
79
+ if (await channel.textContent() === outputChannelName) {
80
+ await channel.click();
81
+ }
82
+ }
83
+ return this.isOutputChannelSelected(outputChannelName);
84
+ }
85
+ return false;
86
+ }
87
+ }
@@ -27,6 +27,11 @@ export class TheiaQuickCommandPalette extends TheiaPageObject {
27
27
  await this.page.waitForSelector(this.selector);
28
28
  }
29
29
 
30
+ async hide(): Promise<void> {
31
+ await this.page.keyboard.press('Escape');
32
+ await this.page.waitForSelector(this.selector, { state: 'hidden' });
33
+ }
34
+
30
35
  async isOpen(): Promise<boolean> {
31
36
  try {
32
37
  await this.page.waitForSelector(this.selector, { timeout: 5000 });
@@ -54,14 +59,17 @@ export class TheiaQuickCommandPalette extends TheiaPageObject {
54
59
  await this.page.keyboard.press('Enter');
55
60
  }
56
61
 
57
- async type(command: string): Promise<void> {
62
+ async type(value: string, confirm = false): Promise<void> {
58
63
  if (!await this.isOpen()) {
59
64
  this.open();
60
65
  }
61
66
  const input = await this.page.waitForSelector(`${this.selector} .monaco-inputbox .input`);
62
67
  if (input != null) {
63
68
  await input.focus();
64
- await input.type(command, { delay: USER_KEY_TYPING_DELAY });
69
+ await input.type(value, { delay: USER_KEY_TYPING_DELAY });
70
+ if (confirm) {
71
+ await this.page.keyboard.press('Enter');
72
+ }
65
73
  }
66
74
  }
67
75
 
@@ -0,0 +1,69 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ElementHandle } from '@playwright/test';
18
+ import { TheiaApp } from './theia-app';
19
+ import { TheiaContextMenu } from './theia-context-menu';
20
+ import { TheiaMenu } from './theia-menu';
21
+ import { TheiaView } from './theia-view';
22
+
23
+ export class TheiaTerminal extends TheiaView {
24
+
25
+ constructor(tabId: string, app: TheiaApp) {
26
+ super({
27
+ tabSelector: `#shell-tab-terminal-${getTerminalId(tabId)}`,
28
+ viewSelector: `#terminal-${getTerminalId(tabId)}`
29
+ }, app);
30
+ }
31
+
32
+ async submit(text: string): Promise<void> {
33
+ await this.write(text);
34
+ const input = await this.waitForInputArea();
35
+ await input.press('Enter');
36
+ }
37
+
38
+ async write(text: string): Promise<void> {
39
+ await this.activate();
40
+ const input = await this.waitForInputArea();
41
+ await input.type(text);
42
+ }
43
+
44
+ async contents(): Promise<string> {
45
+ await this.activate();
46
+ await (await this.openContextMenu()).clickMenuItem('Select All');
47
+ await (await this.openContextMenu()).clickMenuItem('Copy');
48
+ return this.page.evaluate('navigator.clipboard.readText()');
49
+ }
50
+
51
+ protected async openContextMenu(): Promise<TheiaMenu> {
52
+ await this.activate();
53
+ return TheiaContextMenu.open(this.app, () => this.waitForVisibleView());
54
+ }
55
+
56
+ protected async waitForInputArea(): Promise<ElementHandle<SVGElement | HTMLElement>> {
57
+ const view = await this.waitForVisibleView();
58
+ return view.waitForSelector('.xterm-helper-textarea');
59
+ }
60
+
61
+ protected async waitForVisibleView(): Promise<ElementHandle<SVGElement | HTMLElement>> {
62
+ return this.page.waitForSelector(this.viewSelector, { state: 'visible' });
63
+ }
64
+
65
+ }
66
+
67
+ function getTerminalId(tabId: string): string {
68
+ return tabId.substring(tabId.lastIndexOf('-') + 1);
69
+ }
@@ -20,9 +20,12 @@ import { join } from 'path';
20
20
  import { TheiaApp } from './theia-app';
21
21
  import { TheiaEditor } from './theia-editor';
22
22
  import { normalizeId, OSUtil, urlEncodePath } from './util';
23
+ import { TheiaMonacoEditor } from './theia-monaco-editor';
23
24
 
24
25
  export class TheiaTextEditor extends TheiaEditor {
25
26
 
27
+ protected monacoEditor: TheiaMonacoEditor;
28
+
26
29
  constructor(filePath: string, app: TheiaApp) {
27
30
  // shell-tab-code-editor-opener:file:///c%3A/Users/user/AppData/Local/Temp/cloud-ws-JBUhb6/sample.txt:1
28
31
  // code-editor-opener:file:///c%3A/Users/user/AppData/Local/Temp/cloud-ws-JBUhb6/sample.txt:1
@@ -30,19 +33,16 @@ export class TheiaTextEditor extends TheiaEditor {
30
33
  tabSelector: normalizeId(`#shell-tab-code-editor-opener:file://${urlEncodePath(join(app.workspace.escapedPath, OSUtil.fileSeparator, filePath))}:1`),
31
34
  viewSelector: normalizeId(`#code-editor-opener:file://${urlEncodePath(join(app.workspace.escapedPath, OSUtil.fileSeparator, filePath))}:1`) + '.theia-editor'
32
35
  }, app);
36
+ this.monacoEditor = new TheiaMonacoEditor(this.viewSelector, app);
33
37
  }
34
38
 
35
39
  async numberOfLines(): Promise<number | undefined> {
36
40
  await this.activate();
37
- const viewElement = await this.viewElement();
38
- const lineElements = await viewElement?.$$('.view-lines .view-line');
39
- return lineElements?.length;
41
+ return this.monacoEditor.numberOfLines();
40
42
  }
41
43
 
42
44
  async textContentOfLineByLineNumber(lineNumber: number): Promise<string | undefined> {
43
- const lineElement = await this.lineByLineNumber(lineNumber);
44
- const content = await lineElement?.textContent();
45
- return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
45
+ return this.monacoEditor.textContentOfLineByLineNumber(lineNumber);
46
46
  }
47
47
 
48
48
  async replaceLineWithLineNumber(text: string, lineNumber: number): Promise<void> {
@@ -57,14 +57,14 @@ export class TheiaTextEditor extends TheiaEditor {
57
57
 
58
58
  async selectLineWithLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
59
59
  await this.activate();
60
- const lineElement = await this.lineByLineNumber(lineNumber);
60
+ const lineElement = await this.monacoEditor.lineByLineNumber(lineNumber);
61
61
  await this.selectLine(lineElement);
62
62
  return lineElement;
63
63
  }
64
64
 
65
65
  async placeCursorInLineWithLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
66
66
  await this.activate();
67
- const lineElement = await this.lineByLineNumber(lineNumber);
67
+ const lineElement = await this.monacoEditor.lineByLineNumber(lineNumber);
68
68
  await this.placeCursorInLine(lineElement);
69
69
  return lineElement;
70
70
  }
@@ -74,28 +74,9 @@ export class TheiaTextEditor extends TheiaEditor {
74
74
  await this.page.keyboard.press('Backspace');
75
75
  }
76
76
 
77
- protected async lineByLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
78
- await this.activate();
79
- const viewElement = await this.viewElement();
80
- const lines = await viewElement?.$$('.view-lines .view-line');
81
- if (!lines) {
82
- throw new Error(`Couldn't retrieve lines of text editor ${this.tabSelector}`);
83
- }
84
-
85
- const linesWithXCoordinates = [];
86
- for (const lineElement of lines) {
87
- const box = await lineElement.boundingBox();
88
- linesWithXCoordinates.push({ x: box ? box.x : Number.MAX_VALUE, lineElement });
89
- }
90
- linesWithXCoordinates.sort((a, b) => a.x.toString().localeCompare(b.x.toString()));
91
- return linesWithXCoordinates[lineNumber - 1].lineElement;
92
- }
93
-
94
77
  async textContentOfLineContainingText(text: string): Promise<string | undefined> {
95
78
  await this.activate();
96
- const lineElement = await this.lineContainingText(text);
97
- const content = await lineElement?.textContent();
98
- return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
79
+ return this.monacoEditor.textContentOfLineContainingText(text);
99
80
  }
100
81
 
101
82
  async replaceLineContainingText(newText: string, oldText: string): Promise<void> {
@@ -105,14 +86,14 @@ export class TheiaTextEditor extends TheiaEditor {
105
86
 
106
87
  async selectLineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
107
88
  await this.activate();
108
- const lineElement = await this.lineContainingText(text);
89
+ const lineElement = await this.monacoEditor.lineContainingText(text);
109
90
  await this.selectLine(lineElement);
110
91
  return lineElement;
111
92
  }
112
93
 
113
94
  async placeCursorInLineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
114
95
  await this.activate();
115
- const lineElement = await this.lineContainingText(text);
96
+ const lineElement = await this.monacoEditor.lineContainingText(text);
116
97
  await this.placeCursorInLine(lineElement);
117
98
  return lineElement;
118
99
  }
@@ -123,7 +104,7 @@ export class TheiaTextEditor extends TheiaEditor {
123
104
  }
124
105
 
125
106
  async addTextToNewLineAfterLineContainingText(textContainedByExistingLine: string, newText: string): Promise<void> {
126
- const existingLine = await this.lineContainingText(textContainedByExistingLine);
107
+ const existingLine = await this.monacoEditor.lineContainingText(textContainedByExistingLine);
127
108
  await this.placeCursorInLine(existingLine);
128
109
  await this.page.keyboard.press('End');
129
110
  await this.page.keyboard.press('Enter');
@@ -131,18 +112,13 @@ export class TheiaTextEditor extends TheiaEditor {
131
112
  }
132
113
 
133
114
  async addTextToNewLineAfterLineByLineNumber(lineNumber: number, newText: string): Promise<void> {
134
- const existingLine = await this.lineByLineNumber(lineNumber);
115
+ const existingLine = await this.monacoEditor.lineByLineNumber(lineNumber);
135
116
  await this.placeCursorInLine(existingLine);
136
117
  await this.page.keyboard.press('End');
137
118
  await this.page.keyboard.press('Enter');
138
119
  await this.page.keyboard.type(newText);
139
120
  }
140
121
 
141
- protected async lineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
142
- const viewElement = await this.viewElement();
143
- return viewElement?.waitForSelector(`.view-lines .view-line:has-text("${text}")`);
144
- }
145
-
146
122
  protected async selectLine(lineElement: ElementHandle<SVGElement | HTMLElement> | undefined): Promise<void> {
147
123
  await lineElement?.click({ clickCount: 3 });
148
124
  }
@@ -151,13 +127,6 @@ export class TheiaTextEditor extends TheiaEditor {
151
127
  await lineElement?.click();
152
128
  }
153
129
 
154
- protected replaceEditorSymbolsWithSpace(content: string): string | Promise<string | undefined> {
155
- // [ ] &nbsp; => \u00a0 -- NO-BREAK SPACE
156
- // [·] &middot; => \u00b7 -- MIDDLE DOT
157
- // [] &zwnj; => \u200c -- ZERO WIDTH NON-JOINER
158
- return content.replace(/[\u00a0\u00b7]/g, ' ').replace(/[\u200c]/g, '');
159
- }
160
-
161
130
  protected async selectedSuggestion(): Promise<ElementHandle<SVGElement | HTMLElement>> {
162
131
  return this.page.waitForSelector(this.viewSelector + ' .monaco-list-row.show-file-icons.focused');
163
132
  }
@@ -0,0 +1,41 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ElementHandle } from '@playwright/test';
18
+ import { TheiaApp } from './theia-app';
19
+ import { TheiaPageObject } from './theia-page-object';
20
+
21
+ export class TheiaToolbarItem extends TheiaPageObject {
22
+ constructor(app: TheiaApp, protected element: ElementHandle<SVGElement | HTMLElement>) {
23
+ super(app);
24
+ }
25
+
26
+ async commandId(): Promise<string | null> {
27
+ return this.element.getAttribute('id');
28
+ }
29
+
30
+ async isEnabled(): Promise<boolean> {
31
+ const classAttribute = await this.element.getAttribute('class');
32
+ if (classAttribute === undefined || classAttribute === null) {
33
+ return false;
34
+ }
35
+ return classAttribute.includes('enabled');
36
+ }
37
+
38
+ async trigger(): Promise<void> {
39
+ await this.element.click();
40
+ }
41
+ }