@opensumi/playwright 2.21.4 → 2.21.5-next-1668665440.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.
- package/lib/debug-view.js +1 -1
- package/lib/diff-editor.d.ts +4 -0
- package/lib/diff-editor.d.ts.map +1 -0
- package/lib/diff-editor.js +8 -0
- package/lib/diff-editor.js.map +1 -0
- package/lib/editor.d.ts +2 -2
- package/lib/editor.d.ts.map +1 -1
- package/lib/editor.js.map +1 -1
- package/lib/explorer-view.d.ts +1 -0
- package/lib/explorer-view.d.ts.map +1 -1
- package/lib/explorer-view.js +30 -9
- package/lib/explorer-view.js.map +1 -1
- package/lib/filetree-view.js +2 -2
- package/lib/filetree-view.js.map +1 -1
- package/lib/opened-editor-view.js +2 -2
- package/lib/opened-editor-view.js.map +1 -1
- package/lib/panel.js +2 -2
- package/lib/panel.js.map +1 -1
- package/lib/scm-view.d.ts +21 -0
- package/lib/scm-view.d.ts.map +1 -0
- package/lib/scm-view.js +65 -0
- package/lib/scm-view.js.map +1 -0
- package/lib/search-view.js +1 -1
- package/lib/source-control-view.d.ts +8 -0
- package/lib/source-control-view.d.ts.map +1 -0
- package/lib/source-control-view.js +45 -0
- package/lib/source-control-view.js.map +1 -0
- package/lib/tests/scm.test.js +41 -3
- package/lib/tests/scm.test.js.map +1 -1
- package/lib/tests/settings.test.d.ts +2 -0
- package/lib/tests/settings.test.d.ts.map +1 -0
- package/lib/tests/settings.test.js +83 -0
- package/lib/tests/settings.test.js.map +1 -0
- package/lib/text-editor.d.ts +2 -2
- package/lib/text-editor.d.ts.map +1 -1
- package/lib/text-editor.js.map +1 -1
- package/lib/tree-node.d.ts +3 -1
- package/lib/tree-node.d.ts.map +1 -1
- package/lib/tree-node.js +3 -3
- package/lib/tree-node.js.map +1 -1
- package/package.json +4 -3
- package/src/app.ts +135 -0
- package/src/component-editor.ts +64 -0
- package/src/constans/index.ts +18 -0
- package/src/context-menu.ts +27 -0
- package/src/debug-view.ts +24 -0
- package/src/diff-editor.ts +3 -0
- package/src/editor.ts +130 -0
- package/src/explorer-view.ts +142 -0
- package/src/filetree-view.ts +28 -0
- package/src/index.ts +19 -0
- package/src/menu-item.ts +40 -0
- package/src/menu.ts +69 -0
- package/src/menubar.ts +53 -0
- package/src/opened-editor-view.ts +28 -0
- package/src/panel.ts +50 -0
- package/src/quick-command-palette.ts +62 -0
- package/src/quick-open-palette.ts +62 -0
- package/src/scm-view.ts +73 -0
- package/src/search-view.ts +44 -0
- package/src/source-control-view.ts +44 -0
- package/src/terminal.ts +22 -0
- package/src/tests/app.test.ts +16 -0
- package/src/tests/debug.test.ts +68 -0
- package/src/tests/editor.test.ts +143 -0
- package/src/tests/explorer-view.test.ts +202 -0
- package/src/tests/hooks/index.ts +13 -0
- package/src/tests/keymaps.test.ts +118 -0
- package/src/tests/language.test.ts +55 -0
- package/src/tests/scm.test.ts +84 -0
- package/src/tests/search-view.test.ts +32 -0
- package/src/tests/settings.test.ts +103 -0
- package/src/tests/workspaces/debug/.sumi/launch.json +15 -0
- package/src/tests/workspaces/debug/index.js +18 -0
- package/src/tests/workspaces/default/editor.js +0 -0
- package/src/tests/workspaces/default/editor2.js +87 -0
- package/src/tests/workspaces/default/editor3.js +0 -0
- package/src/tests/workspaces/default/test/test.js +1 -0
- package/src/tests/workspaces/git-workspace/a.js +0 -0
- package/src/tests/workspaces/language/definition.ts +12 -0
- package/src/tests/workspaces/language/reference.ts +9 -0
- package/src/text-editor.ts +318 -0
- package/src/tree-node.ts +98 -0
- package/src/utils/element.ts +35 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/key.ts +6 -0
- package/src/view-base.ts +11 -0
- package/src/view.ts +90 -0
- package/src/workspace.ts +36 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { OpenSumiApp } from './app';
|
|
2
|
+
import { OpenSumiFileTreeView } from './filetree-view';
|
|
3
|
+
import { OpenSumiOpenedEditorView } from './opened-editor-view';
|
|
4
|
+
import { OpenSumiPanel } from './panel';
|
|
5
|
+
import { OpenSumiTreeNode } from './tree-node';
|
|
6
|
+
|
|
7
|
+
export class OpenSumiExplorerFileStatNode extends OpenSumiTreeNode {
|
|
8
|
+
async getFsPath() {
|
|
9
|
+
return await this.elementHandle.getAttribute('title');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async isFolder() {
|
|
13
|
+
const icon = await this.elementHandle.$("[class*='file_icon___']");
|
|
14
|
+
if (!icon) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const className = await icon.getAttribute('class');
|
|
18
|
+
return className?.includes('folder-icon');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getMenuItemByName(name: string) {
|
|
22
|
+
const contextMenu = await this.openContextMenu();
|
|
23
|
+
const menuItem = await contextMenu.menuItemByName(name);
|
|
24
|
+
return menuItem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async open(preview = true) {
|
|
28
|
+
if (!preview) {
|
|
29
|
+
await this.elementHandle.dblclick();
|
|
30
|
+
} else {
|
|
31
|
+
await this.elementHandle.click();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async isDirty() {
|
|
36
|
+
const classname = await this.elementHandle.getAttribute('class');
|
|
37
|
+
if (classname?.includes('mod_dirty__')) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class OpenSumiExplorerOpenedEditorNode extends OpenSumiTreeNode {
|
|
45
|
+
async getRelativePath() {
|
|
46
|
+
return await (await this.elementHandle.$('[class*="opened_editor_node_description__"]'))?.textContent();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getFsPath() {
|
|
50
|
+
return await this.elementHandle.getAttribute('title');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async isGroup() {
|
|
54
|
+
const icon = await this.elementHandle.waitForSelector("[class*='file_icon___']");
|
|
55
|
+
const className = await icon.getAttribute('class');
|
|
56
|
+
return className?.includes('folder-icon');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getMenuItemByName(name: string) {
|
|
60
|
+
const contextMenu = await this.openContextMenu();
|
|
61
|
+
const menuItem = await contextMenu.menuItemByName(name);
|
|
62
|
+
return menuItem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async open() {
|
|
66
|
+
await this.elementHandle.click();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class OpenSumiExplorerView extends OpenSumiPanel {
|
|
71
|
+
private _fileTreeView: OpenSumiFileTreeView;
|
|
72
|
+
private _openedEditorView: OpenSumiOpenedEditorView;
|
|
73
|
+
|
|
74
|
+
constructor(app: OpenSumiApp) {
|
|
75
|
+
super(app, 'EXPLORER');
|
|
76
|
+
this._openedEditorView = new OpenSumiOpenedEditorView(this.app, 'OPENED EDITORS');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
initFileTreeView(name: string) {
|
|
80
|
+
this._fileTreeView = new OpenSumiFileTreeView(this.app, name);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get fileTreeView() {
|
|
84
|
+
return this._fileTreeView;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get openedEditorView() {
|
|
88
|
+
return this._openedEditorView;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getFileStatTreeNodeByPath(path: string) {
|
|
92
|
+
const treeItems = await (await this.fileTreeView.getViewElement())?.$$('[class*="file_tree_node__"]');
|
|
93
|
+
if (!treeItems) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let node;
|
|
97
|
+
for (const item of treeItems) {
|
|
98
|
+
const title = await item.getAttribute('title');
|
|
99
|
+
if (title?.startsWith('Group')) {
|
|
100
|
+
if (title === path) {
|
|
101
|
+
node = item;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// The title maybe `~/a.js • Untracked`
|
|
106
|
+
if (title?.split(' ')[0]?.endsWith(path)) {
|
|
107
|
+
node = item;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (node) {
|
|
113
|
+
return new OpenSumiExplorerFileStatNode(node, this.app);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getOpenedEditorTreeNodeByPath(path: string) {
|
|
118
|
+
const treeItems = await (await this.openedEditorView.getViewElement())?.$$('[class*="opened_editor_node__"]');
|
|
119
|
+
if (!treeItems) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let node;
|
|
123
|
+
for (const item of treeItems) {
|
|
124
|
+
const title = await item.getAttribute('title');
|
|
125
|
+
if (title?.startsWith('Group')) {
|
|
126
|
+
if (title === path) {
|
|
127
|
+
node = item;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// The title maybe `~/a.js • Untracked`
|
|
132
|
+
if (title?.split(' ')[0]?.endsWith(path)) {
|
|
133
|
+
node = item;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (node) {
|
|
139
|
+
return new OpenSumiExplorerFileStatNode(node, this.app);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { OpenSumiApp } from './app';
|
|
2
|
+
import { OpenSumiView } from './view';
|
|
3
|
+
|
|
4
|
+
export class OpenSumiFileTreeView extends OpenSumiView {
|
|
5
|
+
constructor(app: OpenSumiApp, workspaceName: string) {
|
|
6
|
+
super(app, {
|
|
7
|
+
viewSelector: '[data-view-id="file-explorer"]',
|
|
8
|
+
tabSelector: '[data-view-id="file-explorer"] [tabindex="0"]',
|
|
9
|
+
name: workspaceName,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async getTitleActionByName(name: string) {
|
|
14
|
+
const header = await this.getTabElement();
|
|
15
|
+
if (!header) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await header.hover();
|
|
19
|
+
const titleAction = await header.waitForSelector('[class*="titleActions___"]');
|
|
20
|
+
const actions = await titleAction.$$('[class*="iconAction__"]');
|
|
21
|
+
for (const action of actions) {
|
|
22
|
+
const title = await action.getAttribute('title');
|
|
23
|
+
if (name === title) {
|
|
24
|
+
return action;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from './app';
|
|
2
|
+
export * from './component-editor';
|
|
3
|
+
export * from './context-menu';
|
|
4
|
+
export * from './editor';
|
|
5
|
+
export * from './explorer-view';
|
|
6
|
+
export * from './filetree-view';
|
|
7
|
+
export * from './menu-item';
|
|
8
|
+
export * from './menu';
|
|
9
|
+
export * from './menubar';
|
|
10
|
+
export * from './opened-editor-view';
|
|
11
|
+
export * from './panel';
|
|
12
|
+
export * from './quick-command-palette';
|
|
13
|
+
export * from './search-view';
|
|
14
|
+
export * from './terminal';
|
|
15
|
+
export * from './text-editor';
|
|
16
|
+
export * from './tree-node';
|
|
17
|
+
export * from './view-base';
|
|
18
|
+
export * from './view';
|
|
19
|
+
export * from './workspace';
|
package/src/menu-item.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ElementHandle } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { textContent } from './utils';
|
|
4
|
+
|
|
5
|
+
export class OpenSumiMenuItem {
|
|
6
|
+
constructor(protected element: ElementHandle<SVGElement | HTMLElement>) {}
|
|
7
|
+
|
|
8
|
+
protected labelElementHandle() {
|
|
9
|
+
return this.element.$("[class*='label__']");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
protected shortCutElementHandle() {
|
|
13
|
+
return this.element.$("[class*='shortcut__']");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async label() {
|
|
17
|
+
return textContent(this.labelElementHandle());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async shortCut() {
|
|
21
|
+
return textContent(this.shortCutElementHandle());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async isEnabled() {
|
|
25
|
+
const classAttribute = await this.element.getAttribute('class');
|
|
26
|
+
if (classAttribute === undefined || classAttribute === null) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return !classAttribute.includes('kt-inner-menu-item-disabled');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async click() {
|
|
33
|
+
const action = await this.element.waitForSelector("[class*='menuAction__']");
|
|
34
|
+
await action?.click({ position: { x: 10, y: 10 } });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async hover(): Promise<void> {
|
|
38
|
+
return this.element.hover();
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/menu.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { OpenSumiMenuItem } from './menu-item';
|
|
2
|
+
import { OpenSumiViewBase } from './view-base';
|
|
3
|
+
|
|
4
|
+
export class OpenSumiMenu extends OpenSumiViewBase {
|
|
5
|
+
selector = '.rc-trigger-popup .kt-inner-menu';
|
|
6
|
+
|
|
7
|
+
protected async menuElementHandle() {
|
|
8
|
+
return this.page.waitForSelector(this.selector);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async waitForVisible() {
|
|
12
|
+
await this.page.waitForSelector(this.selector, { state: 'visible' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async isOpen() {
|
|
16
|
+
const menu = await this.menuElementHandle();
|
|
17
|
+
return !!menu && menu.isVisible();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async close() {
|
|
21
|
+
if (!(await this.isOpen())) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
await this.page.mouse.click(0, 0);
|
|
25
|
+
await this.page.waitForSelector(this.selector, { state: 'detached' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async menuItems() {
|
|
29
|
+
const menuHandle = await this.menuElementHandle();
|
|
30
|
+
if (!menuHandle) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const items = await menuHandle.$$('.kt-inner-menu-item');
|
|
34
|
+
return items.map((element) => new OpenSumiMenuItem(element));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async clickMenuItem(name: string) {
|
|
38
|
+
return (await this.page.waitForSelector(this.menuItemSelector(name))).click();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async menuItemByName(name: string) {
|
|
42
|
+
const menuItems = await this.menuItems();
|
|
43
|
+
for (const item of menuItems) {
|
|
44
|
+
const label = await item.label();
|
|
45
|
+
if (label === name) {
|
|
46
|
+
return item;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async menuItemByIndex(index: number) {
|
|
53
|
+
const menuItems = await this.menuItems();
|
|
54
|
+
if (menuItems.length > index) {
|
|
55
|
+
return menuItems[index];
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected menuItemSelector(label = '') {
|
|
61
|
+
return `.kt-inner-menu-item [class*='label___'] >> text=${label}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async visibleMenuItems() {
|
|
65
|
+
const menuItems = await this.menuItems();
|
|
66
|
+
const labels = await Promise.all(menuItems.map((item) => item.label()));
|
|
67
|
+
return labels.filter((label) => !!label);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/menubar.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ElementHandle } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { OpenSumiApp } from './app';
|
|
4
|
+
import { OPENSUMI_VIEW_CONTAINERS } from './constans';
|
|
5
|
+
import { textContent } from './utils';
|
|
6
|
+
import { OpenSumiViewBase } from './view-base';
|
|
7
|
+
|
|
8
|
+
export class OpenSumiMenubar extends OpenSumiViewBase {
|
|
9
|
+
static USER_KEY_TYPING_DELAY = 100;
|
|
10
|
+
|
|
11
|
+
private _menuItems: ElementHandle[];
|
|
12
|
+
|
|
13
|
+
selector = `#${OPENSUMI_VIEW_CONTAINERS.MENUBAR}`;
|
|
14
|
+
|
|
15
|
+
constructor(app: OpenSumiApp) {
|
|
16
|
+
super(app);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getMenubar() {
|
|
20
|
+
return await this.page.waitForSelector(this.selector);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async trigger(group: string, command: string) {
|
|
24
|
+
// group maybe on of this: File, Edit, Selection, View, Go, Terminal, Help
|
|
25
|
+
const sections = await this.getMenuItems();
|
|
26
|
+
let item;
|
|
27
|
+
for (const section of sections) {
|
|
28
|
+
if ((await section.textContent()) === group) {
|
|
29
|
+
item = section;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!item) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await item.click();
|
|
36
|
+
const menu = await this.page.waitForSelector('.kt-dropdown:not(.kt-dropdown-hidden)');
|
|
37
|
+
const menuItems = await menu.$$('.kt-inner-menu-item');
|
|
38
|
+
for (const menu of menuItems) {
|
|
39
|
+
const label = await textContent(menu.$("[class*='label__']"));
|
|
40
|
+
if (command === label) {
|
|
41
|
+
menu.click();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getMenuItems() {
|
|
48
|
+
if (!this._menuItems) {
|
|
49
|
+
return await (await this.getMenubar())?.$$("[class^='menubar___']");
|
|
50
|
+
}
|
|
51
|
+
return this._menuItems;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { OpenSumiApp } from './app';
|
|
2
|
+
import { OpenSumiView } from './view';
|
|
3
|
+
|
|
4
|
+
export class OpenSumiOpenedEditorView extends OpenSumiView {
|
|
5
|
+
constructor(app: OpenSumiApp, workspaceName: string) {
|
|
6
|
+
super(app, {
|
|
7
|
+
viewSelector: '[data-view-id="file-opened-editor"]',
|
|
8
|
+
tabSelector: '[data-view-id="file-opened-editor"] [tabindex="0"]',
|
|
9
|
+
name: workspaceName,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async getTitleActionByName(name: string) {
|
|
14
|
+
const header = await this.getTabElement();
|
|
15
|
+
if (!header) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await header.hover();
|
|
19
|
+
const titleAction = await header.waitForSelector('[class*="titleActions___"]');
|
|
20
|
+
const actions = await titleAction.$$('[class*="iconAction__"]');
|
|
21
|
+
for (const action of actions) {
|
|
22
|
+
const title = await action.getAttribute('title');
|
|
23
|
+
if (name === title) {
|
|
24
|
+
return action;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/panel.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ElementHandle } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { OpenSumiApp } from './app';
|
|
4
|
+
import { OpenSumiViewBase } from './view-base';
|
|
5
|
+
|
|
6
|
+
export abstract class OpenSumiPanel extends OpenSumiViewBase {
|
|
7
|
+
public view: ElementHandle<HTMLElement | SVGElement> | null;
|
|
8
|
+
private whenReady: Promise<void>;
|
|
9
|
+
|
|
10
|
+
constructor(app: OpenSumiApp, private viewId: string) {
|
|
11
|
+
super(app);
|
|
12
|
+
this.whenReady = this.init();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get viewSelector() {
|
|
16
|
+
return `[data-viewlet-id="${this.viewId.toLocaleLowerCase()}"]`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async init() {
|
|
20
|
+
this.view = await this.page.$(this.viewSelector);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async isVisible() {
|
|
24
|
+
await this.whenReady;
|
|
25
|
+
return this.view?.isVisible();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async open() {
|
|
29
|
+
if (!this.viewId) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await this.app.quickOpenPalette.type('view ');
|
|
33
|
+
await this.app.quickOpenPalette.trigger(this.viewId);
|
|
34
|
+
await this.waitForVisible();
|
|
35
|
+
this.view = await this.page.$(this.viewSelector);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async focus() {
|
|
40
|
+
const visible = await this.isVisible();
|
|
41
|
+
if (!visible) {
|
|
42
|
+
await this.open();
|
|
43
|
+
}
|
|
44
|
+
await this.view?.focus();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async waitForVisible() {
|
|
48
|
+
await this.page.waitForSelector(this.viewSelector, { state: 'visible' });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ElementHandle } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { isMacintosh } from '@opensumi/ide-utils';
|
|
4
|
+
|
|
5
|
+
import { OPENSUMI_VIEW_CONTAINERS } from './constans';
|
|
6
|
+
import { OpenSumiViewBase } from './view-base';
|
|
7
|
+
|
|
8
|
+
export class OpenSumiCommandPalette extends OpenSumiViewBase {
|
|
9
|
+
static USER_KEY_TYPING_DELAY = 200;
|
|
10
|
+
|
|
11
|
+
async open() {
|
|
12
|
+
await this.page.keyboard.press(isMacintosh ? 'Meta+Shift+p' : 'Control+Shift+p');
|
|
13
|
+
await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async isOpen(): Promise<boolean> {
|
|
17
|
+
try {
|
|
18
|
+
await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK}`, { timeout: 5000 });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async trigger(...commandName: string[]): Promise<void> {
|
|
26
|
+
for (const command of commandName) {
|
|
27
|
+
await this.triggerSingleCommand(command);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected async triggerSingleCommand(commandName: string): Promise<void> {
|
|
32
|
+
if (!(await this.isOpen())) {
|
|
33
|
+
await this.open();
|
|
34
|
+
}
|
|
35
|
+
let selected = await this.selectedCommand();
|
|
36
|
+
while (!((await selected?.getAttribute('aria-label')) === commandName)) {
|
|
37
|
+
await this.page.keyboard.press('ArrowDown');
|
|
38
|
+
selected = await this.selectedCommand();
|
|
39
|
+
}
|
|
40
|
+
await this.page.keyboard.press('Enter');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async type(command: string): Promise<void> {
|
|
44
|
+
if (!(await this.isOpen())) {
|
|
45
|
+
await this.open();
|
|
46
|
+
}
|
|
47
|
+
const input = await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK_INPUT}`);
|
|
48
|
+
if (input != null) {
|
|
49
|
+
await input.focus();
|
|
50
|
+
await input.type(command, { delay: OpenSumiCommandPalette.USER_KEY_TYPING_DELAY });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async selectedCommand(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
|
|
55
|
+
const command = await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK}`);
|
|
56
|
+
if (!command) {
|
|
57
|
+
throw new Error('No selected command found!');
|
|
58
|
+
}
|
|
59
|
+
const item = await command.$("[class*='item_selected']");
|
|
60
|
+
return item;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ElementHandle } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { isMacintosh } from '@opensumi/ide-utils';
|
|
4
|
+
|
|
5
|
+
import { OPENSUMI_VIEW_CONTAINERS } from './constans';
|
|
6
|
+
import { OpenSumiViewBase } from './view-base';
|
|
7
|
+
|
|
8
|
+
export class OpenSumiQuickOpenPalette extends OpenSumiViewBase {
|
|
9
|
+
static USER_KEY_TYPING_DELAY = 200;
|
|
10
|
+
|
|
11
|
+
async open() {
|
|
12
|
+
await this.page.keyboard.press(isMacintosh ? 'Meta+p' : 'Control+p');
|
|
13
|
+
await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async isOpen(): Promise<boolean> {
|
|
17
|
+
try {
|
|
18
|
+
await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK}`, { timeout: 5000 });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async trigger(...commandName: string[]): Promise<void> {
|
|
26
|
+
for (const command of commandName) {
|
|
27
|
+
await this.triggerSingleCommand(command);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected async triggerSingleCommand(commandName: string): Promise<void> {
|
|
32
|
+
if (!(await this.isOpen())) {
|
|
33
|
+
await this.open();
|
|
34
|
+
}
|
|
35
|
+
let selected = await this.selectedCommand();
|
|
36
|
+
while (!((await selected?.getAttribute('aria-label')) === commandName)) {
|
|
37
|
+
await this.page.keyboard.press('ArrowDown');
|
|
38
|
+
selected = await this.selectedCommand();
|
|
39
|
+
}
|
|
40
|
+
await this.page.keyboard.press('Enter');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async type(command: string): Promise<void> {
|
|
44
|
+
if (!(await this.isOpen())) {
|
|
45
|
+
await this.open();
|
|
46
|
+
}
|
|
47
|
+
const input = await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK_INPUT}`);
|
|
48
|
+
if (input != null) {
|
|
49
|
+
await input.focus();
|
|
50
|
+
await input.type(command, { delay: OpenSumiQuickOpenPalette.USER_KEY_TYPING_DELAY });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async selectedCommand(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
|
|
55
|
+
const command = await this.page.waitForSelector(`#${OPENSUMI_VIEW_CONTAINERS.QUICKPICK}`);
|
|
56
|
+
if (!command) {
|
|
57
|
+
throw new Error('No selected command found!');
|
|
58
|
+
}
|
|
59
|
+
const item = await command.$("[class*='item_selected']");
|
|
60
|
+
return item;
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/scm-view.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { OpenSumiApp } from './app';
|
|
2
|
+
import { OpenSumiFileTreeView } from './filetree-view';
|
|
3
|
+
import { OpenSumiOpenedEditorView } from './opened-editor-view';
|
|
4
|
+
import { OpenSumiPanel } from './panel';
|
|
5
|
+
import { OpenSumiSourceControlView } from './source-control-view';
|
|
6
|
+
import { OpenSumiTreeNode } from './tree-node';
|
|
7
|
+
|
|
8
|
+
export class OpenSumiSCMFileStatNode extends OpenSumiTreeNode {
|
|
9
|
+
async getFsPath() {
|
|
10
|
+
return await this.elementHandle.getAttribute('title');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async isFolder() {
|
|
14
|
+
const icon = await this.elementHandle.$("[class*='file_icon___']");
|
|
15
|
+
if (!icon) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const className = await icon.getAttribute('class');
|
|
19
|
+
return className?.includes('folder-icon');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getMenuItemByName(name: string) {
|
|
23
|
+
const contextMenu = await this.openContextMenu();
|
|
24
|
+
const menuItem = await contextMenu.menuItemByName(name);
|
|
25
|
+
return menuItem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async open() {
|
|
29
|
+
await this.elementHandle.click();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getBadge() {
|
|
33
|
+
const status = await this.elementHandle.$('[class*="scm_tree_node_status___"]');
|
|
34
|
+
return await status?.textContent();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class OpenSumiSCMView extends OpenSumiPanel {
|
|
39
|
+
private _scmView: OpenSumiSourceControlView;
|
|
40
|
+
private _openedEditorView: OpenSumiOpenedEditorView;
|
|
41
|
+
|
|
42
|
+
constructor(app: OpenSumiApp) {
|
|
43
|
+
super(app, 'SCM');
|
|
44
|
+
this._scmView = new OpenSumiSourceControlView(app, 'SOURCE CONTROL');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get scmView() {
|
|
48
|
+
return this._scmView;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get openedEditorView() {
|
|
52
|
+
return this._openedEditorView;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getFileStatTreeNodeByPath(path: string) {
|
|
56
|
+
const treeItems = await (await this.scmView.getViewElement())?.$$('[class*="scm_tree_node___"]');
|
|
57
|
+
if (!treeItems) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let node;
|
|
61
|
+
for (const item of treeItems) {
|
|
62
|
+
const title = await item.getAttribute('title');
|
|
63
|
+
// title maybe `a.js • Untracked`
|
|
64
|
+
if (title?.split(' ')[0]?.endsWith(path)) {
|
|
65
|
+
node = item;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (node) {
|
|
70
|
+
return new OpenSumiSCMFileStatNode(node, this.app);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { OpenSumiApp } from './app';
|
|
2
|
+
import { OpenSumiPanel } from './panel';
|
|
3
|
+
|
|
4
|
+
export class OpenSumiSearchView extends OpenSumiPanel {
|
|
5
|
+
constructor(app: OpenSumiApp) {
|
|
6
|
+
super(app, 'SEARCH');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get searchInputSelector() {
|
|
10
|
+
return '#search-input-field';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get replaceInputSelector() {
|
|
14
|
+
return '#replace-input-field';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getSearchResult() {
|
|
18
|
+
let results;
|
|
19
|
+
let files;
|
|
20
|
+
const resultElement = await this.app.page.waitForSelector('[class*="result_describe__"]');
|
|
21
|
+
const text = await resultElement.textContent();
|
|
22
|
+
const regx = /^(\d+) results found in (\d+) files/gi;
|
|
23
|
+
if (text) {
|
|
24
|
+
const match = regx.exec(text);
|
|
25
|
+
if (match) {
|
|
26
|
+
results = Number(match[1]);
|
|
27
|
+
files = Number(match[2]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
results,
|
|
32
|
+
files,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async focusOnSearch() {
|
|
37
|
+
const visible = await this.isVisible();
|
|
38
|
+
if (!visible) {
|
|
39
|
+
await this.open();
|
|
40
|
+
}
|
|
41
|
+
const $searchInput = await this.app.page.$(this.searchInputSelector);
|
|
42
|
+
await $searchInput?.focus();
|
|
43
|
+
}
|
|
44
|
+
}
|