Skip to content

Commit

Permalink
feat: Add support for array string settings
Browse files Browse the repository at this point in the history
Signed-off-by: Dominik Jelinek <[email protected]>
  • Loading branch information
djelinek committed Apr 12, 2024
1 parent 75b9be2 commit 64b2dcc
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 17 deletions.
18 changes: 13 additions & 5 deletions docs/Setting.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
![setting](https://user-images.githubusercontent.com/4181232/62535346-76668900-b84b-11e9-8aa3-a07f25e1e37e.png)

#### Lookup

Settings can be located through a [[SettingsEditor]] object:

```typescript
import { Workbench } from 'vscode-extension-tester';
...
Expand All @@ -13,6 +15,7 @@ const setting = await settingsEditor.findSetting('Auto Save', 'Files');
```

#### Retrieve Information

```typescript
// get the title
const title = setting.getTitle();
Expand All @@ -25,7 +28,9 @@ const decription = await setting.getDescription();
```

#### Handling Values

All setting types share the same functions to manipulate their values, however the value types and possible options vary between setting types.

```typescript
// generic value retrieval
const value = await setting.getValue();
Expand All @@ -35,8 +40,11 @@ await setting.setValue('off');
```

##### Setting Value Types
Currently, there are four supported types of setting values: text box, combo box, checkbox and link.
- Text box allows putting in an arbitrary string value, though there might be value checks afterwards that are not handled by this class.
- Combo box only allows inputs from its range of options. If you cast the setting to `ComboSetting`, you will be able to retrieve these options by calling the `getValues` method.
- Checkbox only accepts boolean values, other values are ignored
- Link does not have any value, `getValue` and `setValue` throw an error. Instead, casting the object to `LinkSetting` will allow you to call the `openLink` method, which will open settings.json file in a text editor.

Currently, there are four supported types of setting values: **text box**, **combo box**, **checkbox**, **link** and **array of strings**.

- **Text box** allows putting in an arbitrary string value, though there might be value checks afterwards that are not handled by this class.
- **Combo box** only allows inputs from its range of options. If you cast the setting to `ComboSetting`, you will be able to retrieve these options by calling the `getValues` method.
- **Check box** only accepts boolean values, other values are ignored
- **Link** does not have any value, `getValue` and `setValue` throw an error. Instead, casting the object to `LinkSetting` will allow you to call the `openLink` method, which will open settings.json file in a text editor.
- **Array** settings are supported for type `string`. Each row of array is represented by `ArraySettingItem`.
10 changes: 8 additions & 2 deletions docs/SettingsEditor.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
![settings](https://user-images.githubusercontent.com/4181232/62535349-78304c80-b84b-11e9-80ae-25b587f11354.png)

#### Lookup

Settings editor can be opened through variety of ways, recommended way is using the [[Workbench]] class:

```typescript
import { Workbench, SettingsEditor } from 'vscode-extension-tester'
...
const settingsEditor = await new Workbench().openSettings();
```

#### Find a Setting Item in the Editor
Search for a setting with a given name and category, see more about the [[Setting]] object:

Search for a setting with a given name and category, see more about the [[Setting]] object:

```typescript
// look for a setting named 'Auto Save' under 'Editor' category
const setting = await settingsEditor.findSetting('Auto Save', 'Editor');
Expand All @@ -19,8 +23,10 @@ const setting1 = await settingsEditor.findSetting('Enable', 'Files', 'Simple Dia
```

#### Switch Settings Perspectives

VSCode has two perspectives for its settings: 'User' and 'Workspace'. If your VSCode instance loads from both user and workspace settings.json files, you will be able to switch the perspectives in the editor:

```typescript
// switch to Workspace perspective
await settingsEditor.switchToPerspective('Workspace');
```
```
12 changes: 11 additions & 1 deletion packages/locators/lib/1.37.0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,17 @@ const editor = {
checkboxSetting: By.className('setting-value-checkbox'),
checkboxChecked: 'aria-checked',
linkButton: By.className('edit-in-settings-button'),
itemCount: By.className('settings-count-widget')
itemCount: By.className('settings-count-widget'),
arraySetting: By.className('setting-item-control'),
arrayRoot: By.xpath(`.//div[@role='list' and contains(@class, 'setting-list-widget')]`),
arrayRow: By.className('setting-list-row'),
arrayRowValue: By.className('setting-list-value'),
arrayNewRow: By.className('setting-list-new-row'),
arrayEditRow: By.className('setting-list-edit-row'),
arrayBtnConstructor: (label: string) => By.xpath(`.//a[contains(@role, 'button') and @aria-label='${label}']`),
arraySettingItem: {
btnConstructor: (label: string) => By.xpath(`.//a[contains(@role, 'button') and text()='${label}']`)
}
},
DiffEditor: {
originalEditor: By.className('original-in-monaco-diff-editor'),
Expand Down
201 changes: 196 additions & 5 deletions packages/page-objects/src/components/editor/SettingsEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class SettingsEditor extends Editor {
* Context menu is disabled in this editor, throw an error
*/
async openContextMenu(): Promise<ContextMenu> {
throw new Error('Operation not supported');
throw new Error('Operation not supported!');
}

private async createSetting(element: WebElement, title: string, category: string): Promise<Setting> {
Expand All @@ -132,7 +132,13 @@ export class SettingsEditor extends Editor {
await element.findElement(SettingsEditor.locators.SettingsEditor.linkButton);
return new LinkSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this);
} catch (err) {
throw new Error('Setting type not supported');
// try array setting
try {
await element.findElement(SettingsEditor.locators.SettingsEditor.arraySetting);
return new ArraySetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this);
} catch (err) {
throw new Error('Setting type not supported!');
}
}
}
}
Expand All @@ -142,7 +148,7 @@ export class SettingsEditor extends Editor {

/**
* Abstract item representing a Setting with title, description and
* an input element (combo/textbox/checkbox/link)
* an input element (combo/textbox/checkbox/link/array)
*/
export abstract class Setting extends AbstractElement {

Expand Down Expand Up @@ -274,11 +280,11 @@ export class CheckboxSetting extends Setting {
export class LinkSetting extends Setting {

async getValue(): Promise<string> {
throw new Error('Method getValue is not available for LinkSetting');
throw new Error('Method getValue is not available for LinkSetting!');
}

async setValue(value: string | boolean): Promise<void> {
throw new Error('Method setValue is not available for LinkSetting');
throw new Error('Method setValue is not available for LinkSetting!');
}

/**
Expand All @@ -290,3 +296,188 @@ export class LinkSetting extends Setting {
await link.click();
}
}

/**
* TODO
*/
export class ArraySetting extends Setting {

/**
* @deprecated Method 'getValue' is not available for ArraySetting!
*/
async getValue(): Promise<string | boolean> {
throw new Error('Method \'getValue\' is not available for ArraySetting!');
}

/**
* @deprecated Method 'setValue' is not available for ArraySetting!
*/
async setValue(value: string | boolean): Promise<void> {
throw new Error('Method \'setValue\' is not available for ArraySetting!');
}

/**
* TODO
*/
async select(item: string | number): Promise<void> {
const toSelect = await this.getItem(item);
await toSelect?.select()
}

/**
* TODO
*/
async getItem(item: string | number): Promise<ArraySettingItem | undefined> {
const row = await this.findRow(item);
if(row) {
return new ArraySettingItem(row, this);
}
return undefined;
}

/**
* TODO
*/
async getItems(): Promise<ArraySettingItem[]> {
const listRows = await this.getRows();
let items: ArraySettingItem[] = [];
for(const row of listRows) {
items.push(new ArraySettingItem(row, this));
}
return items;
}

/**
* TODO
*/
async getValues(): Promise<string[]> {
const items = await this.getItems();
let values: string[] = [];
for(const item of items) {
values.push(await item.getValue());
}
return values;
}

/**
* TODO
*/
async add(): Promise<ArraySettingItem> {
// click 'Add Item' button
const button = await this.findElement(SettingsEditor.locators.SettingsEditor.arrayNewRow).findElement(By.className('monaco-button'));
await button.click();

// get item row switched to 'edit' mode
const list = await this.getListRootElement();
const editRow = await list.findElement(SettingsEditor.locators.SettingsEditor.arrayEditRow);
return new ArraySettingItem(editRow, this);
}

/**
* TODO
*/
async edit(item: string | number): Promise<ArraySettingItem | undefined> {
const row = await this.findRow(item);
if(row) {
// select item row
const toEdit = new ArraySettingItem(row, this);
await toEdit.select();

// click 'Edit Item' button
const edit = await toEdit.findElement(SettingsEditor.locators.SettingsEditor.arrayBtnConstructor('Edit Item'));
await edit.click();

// get item row switched to 'edit' mode
const list = await this.getListRootElement();
const editRow = await list.findElement(SettingsEditor.locators.SettingsEditor.arrayEditRow);
return new ArraySettingItem(editRow, this);
}
return undefined;
}

private async getListRootElement(): Promise<WebElement> {
return await this.findElement(SettingsEditor.locators.SettingsEditor.arrayRoot);
}

private async getRows(): Promise<WebElement[]> {
const list = await this.getListRootElement();
return await list.findElements(SettingsEditor.locators.SettingsEditor.arrayRow);
}

private async findRow(item: string | number): Promise<WebElement | undefined> {
const listRows = await this.getRows();
if(Number.isInteger(item)) {
const index = +item;
if (index < 0 || index > listRows.length - 1) {
throw Error(`Index '${index}' is of bounds! Found items have length = ${listRows.length}.`);
}
return listRows[index];
} else {
for(const row of listRows) {
const li = await row.findElement(SettingsEditor.locators.SettingsEditor.arrayRowValue);
if(await li.getText() === item) {
return row;
}
}
}
return undefined;
}
}

/**
* TODO
*/
export class ArraySettingItem extends AbstractElement {

constructor(element: WebElement, setting: ArraySetting) {
super(element, setting);
}

/**
* TODO
*/
async select(): Promise<void> {
await this.click();
}

/**
* TODO
*/
async getValue(): Promise<string> {
return await this.getText();
}

/**
* TODO
*/
async setValue(value: string): Promise<void> {
const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting);
await input.clear();
await input.sendKeys(value);
}

/**
* TODO
*/
async remove(): Promise<void> {
await this.select();
const remove = await this.findElement(SettingsEditor.locators.SettingsEditor.arrayBtnConstructor('Remove Item'));
await remove.click();
}

/**
* TODO
*/
async ok(): Promise<void> {
const ok = await this.findElement(SettingsEditor.locators.SettingsEditor.arraySettingItem.btnConstructor('OK'));
await ok.click();
}

/**
* TODO
*/
async cancel(): Promise<void> {
const cancel = await this.findElement(SettingsEditor.locators.SettingsEditor.arraySettingItem.btnConstructor('Cancel'));
await cancel.click();
}
}
10 changes: 10 additions & 0 deletions packages/page-objects/src/locators/locators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ export interface Locators {
checkboxChecked: string
linkButton: By
itemCount: By
arraySetting: By
arrayRoot: By
arrayRow: By
arrayRowValue: By
arrayNewRow: By
arrayEditRow: By
arrayBtnConstructor: (label: string) => By
arraySettingItem: {
btnConstructor: (label: string) => By
}
}
DiffEditor: {
originalEditor: By
Expand Down
15 changes: 14 additions & 1 deletion tests/test-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
}
],
"configuration": {
"title": "Test Project",
"title": "ExTester Tests",
"properties": {
"testProject.general.helloWorld": {
"type": "boolean",
Expand All @@ -151,6 +151,19 @@
"testProject.enableCodeLens": {
"type": "boolean",
"default": false
},
"testProject.general.helloWorldArray": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
},
"additionalProperties": false,
"markdownDescription": "This is an example array of strings",
"default": [
"Hello World",
"Hello ExTester"
]
}
}
},
Expand Down
Loading

0 comments on commit 64b2dcc

Please sign in to comment.