Skip to content

Commit

Permalink
Add support for handling option preferences
Browse files Browse the repository at this point in the history
This additional feature for handling option preferences is a
prerequisite for fixing
eclipse-theia#10891

Change-Id: I1cabb47ff21ab912ae8794c63d16143de9dc77e9
  • Loading branch information
planger committed Mar 30, 2022
1 parent 7b8d3b7 commit 566ea80
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 81 deletions.
147 changes: 75 additions & 72 deletions examples/playwright/src/theia-preference-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const TheiaSettingsViewData = {
};

export const PreferenceIds = {
Editor: {
AutoSave: 'editor.autoSave',
RenderWhitespace: 'editor.renderWhitespace'
},
Explorer: {
AutoReveal: 'explorer.autoReveal'
},
Expand All @@ -33,6 +37,19 @@ export const PreferenceIds = {
};

export const DefaultPreferences = {
Editor: {
AutoSave: {
On: 'on',
Off: 'off'
},
RenderWhitespace: {
None: 'none',
Boundary: 'boundary',
Selection: 'selection',
Trailing: 'trailing',
All: 'all'
}
},
Explorer: {
AutoReveal: {
Enabled: true
Expand All @@ -50,6 +67,7 @@ export enum TheiaPreferenceScope {

export class TheiaPreferenceView extends TheiaView {
public customTimeout?: number;
protected modificationIndicator = '.theia-mod-item-modified';

constructor(app: TheiaApp) {
super(TheiaSettingsViewData, app);
Expand All @@ -72,98 +90,81 @@ export class TheiaPreferenceView extends TheiaView {
await scopeTab.click();
}

async getBooleanPreferenceByPath(sectionTitle: string, name: string): Promise<boolean> {
const preferenceId = await this.findPreferenceId(sectionTitle, name);
return this.getBooleanPreferenceById(preferenceId);
}

async getBooleanPreferenceById(preferenceId: string): Promise<boolean> {
const element = await this.findPreferenceEditorById(preferenceId);
return element.isChecked();
}

async getBooleanPreferenceByPath(sectionTitle: string, name: string): Promise<boolean> {
async setBooleanPreferenceByPath(sectionTitle: string, name: string, value: boolean): Promise<void> {
const preferenceId = await this.findPreferenceId(sectionTitle, name);
return this.getBooleanPreferenceById(preferenceId);
return this.setBooleanPreferenceById(preferenceId, value);
}

async setBooleanPreferenceById(preferenceId: string, value: boolean): Promise<void> {
const element = await this.findPreferenceEditorById(preferenceId);
return value ? element.check() : element.uncheck();
}

async setBooleanPreferenceByPath(sectionTitle: string, name: string, value: boolean): Promise<void> {
async getStringPreferenceByPath(sectionTitle: string, name: string): Promise<string> {
const preferenceId = await this.findPreferenceId(sectionTitle, name);
return this.setBooleanPreferenceById(preferenceId, value);
return this.getStringPreferenceById(preferenceId);
}

async getStringPreferenceById(preferenceId: string): Promise<string> {
const element = await this.findPreferenceEditorById(preferenceId);
return element.evaluate(e => (e as HTMLInputElement).value);
}

async getStringPreferenceByPath(sectionTitle: string, name: string): Promise<string> {
async setStringPreferenceByPath(sectionTitle: string, name: string, value: string): Promise<void> {
const preferenceId = await this.findPreferenceId(sectionTitle, name);
return this.getStringPreferenceById(preferenceId);
return this.setStringPreferenceById(preferenceId, value);
}

async setStringPreferenceById(preferenceId: string, value: string): Promise<void> {
const element = await this.findPreferenceEditorById(preferenceId);
return element.fill(value);
}

async setStringPreferenceByPath(sectionTitle: string, name: string, value: string): Promise<void> {
async getOptionsPreferenceByPath(sectionTitle: string, name: string): Promise<string> {
const preferenceId = await this.findPreferenceId(sectionTitle, name);
return this.setStringPreferenceById(preferenceId, value);
return this.getOptionsPreferenceById(preferenceId);
}

async waitForModified(preferenceId: string): Promise<void> {
await this.activate();
const viewElement = await this.viewElement();
await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}.theia-mod-item-modified`, { timeout: this.customTimeout });
async getOptionsPreferenceById(preferenceId: string): Promise<string> {
const element = await this.findPreferenceEditorById(preferenceId, 'select');
return element.evaluate(e => {
const selectElement = (e as HTMLSelectElement);
const option = selectElement.options[selectElement.selectedIndex];
return option.text;
});
}

async resetStringPreferenceById(preferenceId: string): Promise<void> {
const resetPreferenceButton = await this.findPreferenceResetButton(preferenceId);
if (!resetPreferenceButton) {
// preference not modified
return;
}
const previousValue = await this.getStringPreferenceById(preferenceId);
const selector = this.getPreferenceEditorSelector(preferenceId);
const done = await resetPreferenceButton.click();
await this.page.waitForFunction(data => {
const element = document.querySelector(data.selector);
if (!element) {
throw new Error(`Could not find preference element with id "${data.preferenceId}"`);
}
const value = (element as HTMLInputElement).value;
return value !== data.previousValue;
}, { preferenceId, selector, previousValue, done }, { timeout: this.customTimeout });
}

async resetStringPreferenceByPath(sectionTitle: string, name: string): Promise<void> {
async setOptionsPreferenceByPath(sectionTitle: string, name: string, value: string): Promise<void> {
const preferenceId = await this.findPreferenceId(sectionTitle, name);
return this.resetStringPreferenceById(preferenceId);
return this.setOptionsPreferenceById(preferenceId, value);
}

async resetBooleanPreferenceById(preferenceId: string): Promise<void> {
const resetPreferenceButton = await this.findPreferenceResetButton(preferenceId);
if (!resetPreferenceButton) {
// preference not modified
return;
}
const previousValue = await this.getBooleanPreferenceById(preferenceId);
const selector = this.getPreferenceEditorSelector(preferenceId);
const done = await resetPreferenceButton.click();
await this.page.waitForFunction(data => {
const element = document.querySelector(data.selector);
if (!element) {
throw new Error(`Could not find preference element with id "${data.preferenceId}"`);
}
const value = (element as HTMLInputElement).checked;
return value !== data.previousValue;
}, { preferenceId, selector, previousValue, done }, { timeout: this.customTimeout });
}

async resetBooleanPreferenceByPath(sectionTitle: string, name: string): Promise<void> {
async setOptionsPreferenceById(preferenceId: string, value: string): Promise<void> {
const element = await this.findPreferenceEditorById(preferenceId, 'select');
await element.selectOption({ label: value });
}

async resetPreferenceByPath(sectionTitle: string, name: string): Promise<void> {
const preferenceId = await this.findPreferenceId(sectionTitle, name);
return this.resetBooleanPreferenceById(preferenceId);
return this.resetPreferenceById(preferenceId);
}

async resetPreferenceById(preferenceId: string): Promise<void> {
// this is just to fail if the preference doesn't exist at all
await this.findPreferenceEditorById(preferenceId, '');
const resetPreferenceButton = await this.findPreferenceResetButton(preferenceId);
await resetPreferenceButton.click();
await this.waitForUnmodified(preferenceId);
}

private async findPreferenceId(sectionTitle: string, name: string): Promise<string> {
Expand All @@ -178,9 +179,9 @@ export class TheiaPreferenceView extends TheiaView {
return preferenceId;
}

private async findPreferenceEditorById(preferenceId: string): Promise<ElementHandle<SVGElement | HTMLElement>> {
private async findPreferenceEditorById(preferenceId: string, elementType: string = 'input'): Promise<ElementHandle<SVGElement | HTMLElement>> {
const viewElement = await this.viewElement();
const element = await viewElement?.waitForSelector(this.getPreferenceEditorSelector(preferenceId), { timeout: this.customTimeout });
const element = await viewElement?.waitForSelector(this.getPreferenceEditorSelector(preferenceId, elementType), { timeout: this.customTimeout });
if (!element) {
throw new Error(`Could not find element with preference id "${preferenceId}"`);
}
Expand All @@ -191,26 +192,13 @@ export class TheiaPreferenceView extends TheiaView {
return `li[data-pref-id="${preferenceId}"]`;
}

private getPreferenceEditorSelector(preferenceId: string): string {
return `${this.getPreferenceSelector(preferenceId)} input`;
private getPreferenceEditorSelector(preferenceId: string, elementType: string): string {
return `${this.getPreferenceSelector(preferenceId)} ${elementType}`;
}

private getPreferenceGutterSelector(preferenceId: string): string {
return `${this.getPreferenceSelector(preferenceId)} .pref-context-gutter`;
}

private async findPreferenceResetButton(preferenceId: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
private async findPreferenceResetButton(preferenceId: string): Promise<ElementHandle<SVGElement | HTMLElement>> {
await this.activate();
const viewElement = await this.viewElement();
const gutter = await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}`, { timeout: this.customTimeout });
if (!gutter) {
throw new Error(`Could not determine modified state for element with preference id "${preferenceId}"`);
}
const isModified = await gutter.evaluate(e => e.classList.contains('theia-mod-item-modified'));
if (!isModified) {
return undefined;
}

const settingsContextMenuBtn = await viewElement?.waitForSelector(`${this.getPreferenceSelector(preferenceId)} .settings-context-menu-btn`);
if (!settingsContextMenuBtn) {
throw new Error(`Could not find context menu button for element with preference id "${preferenceId}"`);
Expand All @@ -223,4 +211,19 @@ export class TheiaPreferenceView extends TheiaView {
return resetPreferenceButton;
}

async waitForModified(preferenceId: string): Promise<void> {
await this.activate();
const viewElement = await this.viewElement();
await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}${this.modificationIndicator}`, { timeout: this.customTimeout });
}

async waitForUnmodified(preferenceId: string): Promise<void> {
await this.activate();
const viewElement = await this.viewElement();
await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}${this.modificationIndicator}`, { state: 'detached', timeout: this.customTimeout });
}

private getPreferenceGutterSelector(preferenceId: string): string {
return `${this.getPreferenceSelector(preferenceId)} .pref-context-gutter`;
}
}
43 changes: 34 additions & 9 deletions examples/playwright/tests/theia-preference-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,44 +38,63 @@ test.describe('Preference View', () => {
const preferences = await app.openPreferences(TheiaPreferenceView);
const preferenceId = PreferenceIds.DiffEditor.MaxComputationTime;

await preferences.resetStringPreferenceById(preferenceId);
await preferences.resetPreferenceById(preferenceId);
expect(await preferences.getStringPreferenceById(preferenceId)).toBe(DefaultPreferences.DiffEditor.MaxComputationTime);

await preferences.setStringPreferenceById(preferenceId, '8000');
await preferences.waitForModified(preferenceId);
expect(await preferences.getStringPreferenceById(preferenceId)).toBe('8000');

await preferences.resetStringPreferenceById(preferenceId);
await preferences.resetPreferenceById(preferenceId);
expect(await preferences.getStringPreferenceById(preferenceId)).toBe(DefaultPreferences.DiffEditor.MaxComputationTime);
});

test('should be able to read, set, and reset Boolean preferences', async () => {
const preferences = await app.openPreferences(TheiaPreferenceView);
const preferenceId = PreferenceIds.Explorer.AutoReveal;

await preferences.resetBooleanPreferenceById(preferenceId);
await preferences.resetPreferenceById(preferenceId);
expect(await preferences.getBooleanPreferenceById(preferenceId)).toBe(DefaultPreferences.Explorer.AutoReveal.Enabled);

await preferences.setBooleanPreferenceById(preferenceId, false);
await preferences.waitForModified(preferenceId);
expect(await preferences.getBooleanPreferenceById(preferenceId)).toBe(false);

await preferences.resetBooleanPreferenceById(preferenceId);
await preferences.resetPreferenceById(preferenceId);
expect(await preferences.getBooleanPreferenceById(preferenceId)).toBe(DefaultPreferences.Explorer.AutoReveal.Enabled);
});

test('should be able to read, set, and reset Options preferences', async () => {
const preferences = await app.openPreferences(TheiaPreferenceView);
const preferenceId = PreferenceIds.Editor.RenderWhitespace;

await preferences.resetPreferenceById(preferenceId);
expect(await preferences.getOptionsPreferenceById(preferenceId)).toBe(DefaultPreferences.Editor.RenderWhitespace.None);

await preferences.setOptionsPreferenceById(preferenceId, DefaultPreferences.Editor.RenderWhitespace.Boundary);
await preferences.waitForModified(preferenceId);
expect(await preferences.getOptionsPreferenceById(preferenceId)).toBe(DefaultPreferences.Editor.RenderWhitespace.Boundary);

await preferences.resetPreferenceById(preferenceId);
expect(await preferences.getOptionsPreferenceById(preferenceId)).toBe(DefaultPreferences.Editor.RenderWhitespace.None);
});

test('should throw an error if we try to read, set, or reset a non-existing preference', async () => {
const preferences = await app.openPreferences(TheiaPreferenceView);

preferences.customTimeout = 500;
try {
await expect(preferences.getBooleanPreferenceById('no.a.real.preference')).rejects.toThrowError();
await expect(preferences.setBooleanPreferenceById('no.a.real.preference', true)).rejects.toThrowError();
await expect(preferences.resetBooleanPreferenceById('no.a.real.preference')).rejects.toThrowError();
await expect(preferences.resetPreferenceById('no.a.real.preference')).rejects.toThrowError();

await expect(preferences.getStringPreferenceById('no.a.real.preference')).rejects.toThrowError();
await expect(preferences.setStringPreferenceById('no.a.real.preference', 'a')).rejects.toThrowError();
await expect(preferences.resetStringPreferenceById('no.a.real.preference')).rejects.toThrowError();
await expect(preferences.resetPreferenceById('no.a.real.preference')).rejects.toThrowError();

await expect(preferences.getOptionsPreferenceById('no.a.real.preference')).rejects.toThrowError();
await expect(preferences.setOptionsPreferenceById('no.a.real.preference', 'a')).rejects.toThrowError();
await expect(preferences.resetPreferenceById('no.a.real.preference')).rejects.toThrowError();
} finally {
preferences.customTimeout = undefined;
}
Expand All @@ -86,8 +105,14 @@ test.describe('Preference View', () => {
const stringPreference = PreferenceIds.DiffEditor.MaxComputationTime;
const booleanPreference = PreferenceIds.Explorer.AutoReveal;

await expect(preferences.getBooleanPreferenceById(stringPreference)).rejects.toThrowError();
await expect(preferences.setBooleanPreferenceById(stringPreference, true)).rejects.toThrowError();
await expect(preferences.setStringPreferenceById(booleanPreference, 'true')).rejects.toThrowError();
preferences.customTimeout = 500;
try {
await expect(preferences.getBooleanPreferenceById(stringPreference)).rejects.toThrowError();
await expect(preferences.setBooleanPreferenceById(stringPreference, true)).rejects.toThrowError();
await expect(preferences.setStringPreferenceById(booleanPreference, 'true')).rejects.toThrowError();
await expect(preferences.setOptionsPreferenceById(booleanPreference, 'true')).rejects.toThrowError();
} finally {
preferences.customTimeout = undefined;
}
});
});

0 comments on commit 566ea80

Please sign in to comment.