-
Notifications
You must be signed in to change notification settings - Fork 19
/
EditorView.ts
384 lines (349 loc) · 12.5 KB
/
EditorView.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
import type { ChainablePromiseElement } from 'webdriverio'
import { TextEditor, DiffEditor, SettingsEditor } from '../index.js'
import {
PageDecorator, IPageDecorator, BasePage, ElementWithContextMenu, VSCodeLocatorMap
} from '../utils.js'
import {
EditorView as EditorViewLocators,
Editor as EditorLocatorsObj
} from '../../locators/1.73.0.js'
export interface EditorView extends IPageDecorator<typeof EditorViewLocators> {}
/**
* View handling the open editors
*
* @category Editor
*/
@PageDecorator(EditorViewLocators)
export class EditorView extends BasePage<typeof EditorViewLocators> {
/**
* @private
*/
public locatorKey = 'EditorView' as const
/**
* Switch to an editor tab with the given title
* @param title title of the tab
* @param groupIndex zero based index for the editor group (0 for the left most group)
* @returns Promise resolving to Editor object
*/
async openEditor (title: string, groupIndex = 0) {
const group = await this.getEditorGroup(groupIndex)
return group.openEditor(title)
}
/**
* Close an editor tab with the given title
* @param title title of the tab
* @param groupIndex zero based index for the editor group (0 for the left most group)
* @returns Promise resolving when the tab's close button is pressed
*/
async closeEditor (title: string, groupIndex = 0): Promise<void> {
const group = await this.getEditorGroup(groupIndex)
return group.closeEditor(title)
}
/**
* Close all open editor tabs
* @param groupIndex optional index to specify an editor group
* @returns Promise resolving once all tabs have had their close button pressed
*/
async closeAllEditors (groupIndex?: number): Promise<void> {
let groups = await this.getEditorGroups()
if (groupIndex !== undefined) {
await groups[0].closeAllEditors()
return
}
while (groups.length > 0 && (await groups[0].getOpenEditorTitles()).length > 0) {
await groups[0].closeAllEditors()
groups = await this.getEditorGroups()
}
}
/**
* Retrieve all open editor tab titles in an array
* @param groupIndex optional index to specify an editor group, if left empty will search all groups
* @returns Promise resolving to array of editor titles
*/
async getOpenEditorTitles (groupIndex?: number): Promise<string[]> {
const groups = await this.getEditorGroups()
if (groupIndex !== undefined) {
return groups[groupIndex].getOpenEditorTitles()
}
const titles: string[] = []
for (const group of groups) {
titles.push(...(await group.getOpenEditorTitles()))
}
return titles
}
/**
* Retrieve an editor tab from a given group by title
* @param title title of the tab
* @param groupIndex zero based index of the editor group, default 0 (leftmost one)
* @returns promise resolving to EditorTab object
*/
async getTabByTitle (title: string, groupIndex = 0): Promise<EditorTab> {
const group = await this.getEditorGroup(groupIndex)
return group.getTabByTitle(title)
}
/**
* Retrieve all open editor tabs
* @param groupIndex index of group to search for tabs, if left undefined, all groups are searched
* @returns promise resolving to EditorTab list
*/
async getOpenTabs (groupIndex?: number): Promise<EditorTab[]> {
const groups = await this.getEditorGroups()
if (groupIndex !== undefined) {
return groups[groupIndex].getOpenTabs()
}
const tabs: EditorTab[] = []
for (const group of groups) {
tabs.push(...(await group.getOpenTabs()))
}
return tabs
}
/**
* Retrieve the active editor tab
* @returns promise resolving to EditorTab object, undefined if no tab is active
*/
async getActiveTab (): Promise<EditorTab | undefined> {
const tabs = await this.getOpenTabs()
const klasses = await Promise.all(tabs.map(async (tab) => tab.elem.getAttribute('class')))
const index = klasses.findIndex((klass) => klass.indexOf('active') > -1)
if (index > -1) {
return tabs[index]
}
return undefined
}
/**
* Retrieve all editor groups in a list, sorted left to right
* @returns promise resolving to an array of EditorGroup objects
*/
async getEditorGroups (): Promise<EditorGroup[]> {
const groups: EditorGroup[] = []
for await (const elements of this.editorGroup$$) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
groups.push(await (new EditorGroup(this.locatorMap, elements as any, this).poll()))
}
// sort the groups by x coordinates, so the leftmost is always at index 0
for (let i = 0; i < groups.length - 1; i += 1) {
for (let j = 0; j < groups.length - i - 1; j += 1) {
if ((await groups[j].elem.getLocation('x')) > (await groups[j + 1].elem.getLocation('x'))) {
const temp = groups[j]
groups[j] = groups[j + 1]
groups[j + 1] = temp
}
}
}
return groups
}
/**
* Retrieve an editor group with a given index (counting from left to right)
* @param index zero based index of the editor group (leftmost group has index 0)
* @returns promise resolving to an EditorGroup object
*/
async getEditorGroup (index: number): Promise<EditorGroup> {
return (await this.getEditorGroups())[index]
}
/**
* Get editor actions of a select editor group
* @param groupIndex zero based index of the editor group (leftmost group has index 0), default 0
* @returns promise resolving to list of WebElement objects
*/
async getActions (groupIndex = 0) {
const group = await this.getEditorGroup(groupIndex)
return group.getActions()
}
/**
* Get editor action of a select editor group, search by title
* @param groupIndex zero based index of the editor group (leftmost group has index 0), default 0
* @returns promise resolving to WebElement object if found, undefined otherwise
*/
async getAction (title: string, groupIndex = 0) {
const group = await this.getEditorGroup(groupIndex)
return group.getAction(title)
}
}
export interface EditorGroup extends IPageDecorator<typeof EditorViewLocators> {}
/**
* Page object representing an editor group
*
* @category Editor
*/
@PageDecorator(EditorViewLocators)
export class EditorGroup extends BasePage<typeof EditorViewLocators> {
/**
* @private
*/
public locatorKey = 'EditorView' as const
constructor (
locators: VSCodeLocatorMap,
element: ChainablePromiseElement<WebdriverIO.Element>,
public view = new EditorView(locators)
) {
super(locators, element)
}
/**
* Switch to an editor tab with the given title
* @param title title of the tab
* @returns Promise resolving to Editor object
*/
async openEditor (title: string): Promise<SettingsEditor | DiffEditor | TextEditor> {
const tab = await this.getTabByTitle(title)
await tab.select()
if (await this.settingsEditor$.isExisting()) {
return new SettingsEditor(
this.locatorMap,
this
).wait()
}
if (await this.diffEditor$.isExisting()) {
return new DiffEditor(
this.locatorMap,
this.locatorMap.Editor.elem as string,
this
).wait()
}
return new TextEditor(
this.locatorMap,
this.locatorMap.Editor.elem as string,
this
).wait()
}
/**
* Close an editor tab with the given title
* @param title title of the tab
* @returns Promise resolving when the tab's close button is pressed
*/
async closeEditor (title: string): Promise<void> {
const tab = await this.getTabByTitle(title)
await tab.elem.moveTo()
const closeButton = await tab.elem.$(this.locators.closeTab)
await closeButton.click()
}
/**
* Close all open editor tabs
* @returns Promise resolving once all tabs have had their close button pressed
*/
async closeAllEditors (): Promise<void> {
let titles = await this.getOpenEditorTitles()
while (titles.length > 0) {
await this.closeEditor(titles[0])
try {
// check if the group still exists
await this.elem.getTagName()
} catch (err) {
break
}
titles = await this.getOpenEditorTitles()
}
}
/**
* Retrieve all open editor tab titles in an array
* @returns Promise resolving to array of editor titles
*/
async getOpenEditorTitles (): Promise<string[]> {
const tabs = await this.tab$$
const titles = []
for (const tab of tabs) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const title = await new EditorTab(this.locatorMap, tab as any, this.view).getTitle()
titles.push(title)
}
return titles
}
/**
* Retrieve an editor tab by title
* @param title title of the tab
* @returns promise resolving to EditorTab object
*/
async getTabByTitle (title: string): Promise<EditorTab> {
const tabs = await this.tab$$
const availableLabels = new Set()
for (const tab of tabs) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const editorTab = new EditorTab(this.locatorMap, tab as any, this.view)
const label = await editorTab.getTitle()
availableLabels.add(label)
if (label === title) {
return editorTab
}
}
throw new Error(
`No editor with title '${title}' found, `
+ `available editor were: ${[...availableLabels].join(', ')}`
)
}
/**
* Retrieve all open editor tabs
* @returns promise resolving to EditorTab list
*/
async getOpenTabs (): Promise<EditorTab[]> {
const tabs = this.tab$$
return tabs.map(async (tab) => (
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
new EditorTab(this.locatorMap, tab as any, this.view).wait()
))
}
/**
* Retrieve the active editor tab
* @returns promise resolving to EditorTab object, undefined if no tab is active
*/
async getActiveTab (): Promise<EditorTab | undefined> {
const tabs = await this.getOpenTabs()
const klasses = await Promise.all(tabs.map(async (tab) => tab.elem.getAttribute('class')))
const index = klasses.findIndex((klass) => klass.indexOf('active') > -1)
if (index > -1) {
return tabs[index]
}
return undefined
}
/**
* Retrieve the editor action buttons as WebElements
* @returns promise resolving to list of WebElement objects
*/
async getActions () {
return this.actionContainer$.$$(this.locators.actionItem)
}
/**
* Find an editor action button by title
* @param title title of the button
* @returns promise resolving to WebElement representing the button if found, undefined otherwise
*/
async getAction (title: string) {
const actions = await this.getActions()
for (const item of actions) {
if (await item.getAttribute('title') === title) {
return item
}
}
return undefined
}
}
export interface EditorTab extends IPageDecorator<typeof EditorLocatorsObj> {}
/**
* Page object for editor view tab
*
* @category Editor
*/
@PageDecorator(EditorLocatorsObj)
export class EditorTab extends ElementWithContextMenu<typeof EditorLocatorsObj> {
/**
* @private
*/
public locatorKey = 'Editor' as const
constructor (
locators: VSCodeLocatorMap,
element: ChainablePromiseElement<WebdriverIO.Element>,
public view: EditorView
) {
super(locators, element)
}
/**
* Get the tab title as string
*/
async getTitle (): Promise<string> {
return this.title$.getText()
}
/**
* Select (click) the tab
*/
async select (): Promise<void> {
await this.elem.click()
}
}