Skip to content

Commit

Permalink
Project import generated by Copybara
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 722886535
  • Loading branch information
jimper authored and copybara-github committed Feb 4, 2025
1 parent a9248fe commit 28ecf8a
Show file tree
Hide file tree
Showing 40 changed files with 438 additions and 172 deletions.
5 changes: 4 additions & 1 deletion src/components/configurator/slot-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/

import '../ui-controls/config-section';
import '../ui-controls/configurator-format-select';
import '../ui-controls/configurator-icon-button';
import '../ui-controls/configurator-format-select';
import '../ui-controls/configurator-text-field';
import '../ui-controls/slot-size-input';
import '../ui-controls/targeting-input';
Expand Down Expand Up @@ -58,6 +58,7 @@ const strings = {
msg('Sample ads (out-of-page)', {desc: 'Option group label'}),
sizeSectionTitle: () =>
msg('Sizes', {desc: 'Section containing ad sizing options.'}),
slotTemplateLabel: () => msg('Slot template', {desc: 'Drop-down label'}),
targetingSectionTitle: () =>
msg('Targeting', {
desc: 'Section containing ad targeting options.',
Expand Down Expand Up @@ -339,6 +340,7 @@ export class SlotSettings extends LitElement {

return html`<configurator-format-select
class="slot-template"
label="${strings.slotTemplateLabel()}"
name="templates"
.options="${options}"
@update="${this.updateSlot}"
Expand All @@ -351,6 +353,7 @@ export class SlotSettings extends LitElement {
{
// Add the "no format selected" option.
label: strings.oopFormatUnselected(),
selected: !slot.format,
value: '',
},
];
Expand Down
23 changes: 15 additions & 8 deletions src/components/ui-controls/configurator-format-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@
* limitations under the License.
*/

import {signal, SignalWatcher} from '@lit-labs/signals';
import {localized, msg} from '@lit/localize';
import {TemplateResult} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {isEqual} from 'lodash-es';

import {
ConfiguratorOptGroup,
ConfiguratorOption,
ConfiguratorSelect,
} from './configurator-select.js';
import {signal, SignalWatcher} from '@lit-labs/signals';
import {TemplateResult} from 'lit';
import {isEqual} from 'lodash-es';

const ANCHOR_FORMATS: OutOfPageFormat[] = ['BOTTOM_ANCHOR', 'TOP_ANCHOR'];

Expand Down Expand Up @@ -145,15 +146,21 @@ export class ConfiguratorFormatSelect extends SignalWatcher(

override renderOption(
option: ConfiguratorFormatSelectOption,
nested = false,
): TemplateResult {
if (this.isOptGroup(option)) return super.renderOption(option);

const formatDisabled = this.isFormatDisabled(option.format);
return super.renderOption({
...option,
disabled: option.disabled || formatDisabled,
label: `${option.label}${formatDisabled ? ` (${strings.formatDisabled()})` : ''}`,
});
return super.renderOption(
{
...option,
disabled: option.disabled || formatDisabled,
label: `${option.label}${
formatDisabled ? ` (${strings.formatDisabled()})` : ''
}`,
},
nested,
);
}

disconnectedCallback() {
Expand Down
89 changes: 62 additions & 27 deletions src/components/ui-controls/configurator-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,29 @@
* limitations under the License.
*/

import '@material/web/select/filled-select';
import '@material/web/select/select-option';

import {localized} from '@lit/localize';
import {MdFilledSelect} from '@material/web/select/filled-select.js';
import {css, html, LitElement, TemplateResult} from 'lit';
import {customElement, property, query} from 'lit/decorators.js';
import {ifDefined} from 'lit/directives/if-defined.js';
import {when} from 'lit/directives/when.js';

/**
* Whether menu animations are enabled.
*
* Menu animations don't seem to play nice with disabled elements
* at present, so keeping this disabled for now.
*/
const MENU_ANIMATION_ENABLED = false;

/**
* The number of spaces to indent options nested under an opt group.
*/
const OPT_GROUP_INDENT = 2;

/**
* Describes an `<option>` element.
*/
Expand Down Expand Up @@ -48,7 +65,7 @@ export type ConfiguratorSelectOption =
@localized()
@customElement('configurator-select')
export class ConfiguratorSelect extends LitElement {
@query('select') selectElement?: HTMLSelectElement;
@query('md-filled-select') selectElement?: MdFilledSelect;

static styles = css`
:host {
Expand All @@ -57,15 +74,22 @@ export class ConfiguratorSelect extends LitElement {
width: 100%;
}
label {
min-width: 125px;
padding: 5px;
padding-inline-start: 0;
md-filled-select {
width: 100%;
--md-filled-field-leading-space: 10px;
--md-filled-field-top-space: calc(0.75rem - 2px);
--md-filled-field-trailing-space: 10px;
--md-filled-field-with-label-bottom-space: 4px;
--md-filled-field-with-label-top-space: 2px;
}
select {
flex-grow: 1;
padding: 5px;
md-select-option {
--md-menu-item-bottom-space: 0;
--md-menu-item-leading-space: 10px;
--md-menu-item-one-line-container-height: 46px;
--md-menu-item-top-space: 0;
--md-menu-item-trailing-space: 10px;
}
`;

Expand Down Expand Up @@ -112,7 +136,7 @@ export class ConfiguratorSelect extends LitElement {
*/
get selectedOptions(): ConfiguratorOption[] {
const options = this.internalOptions.flatMap(option => {
return this.isOptGroup(option) ? option.options : option;
return this.isOptGroup(option) ? [option, ...option.options] : option;
});

// Order of precedence:
Expand Down Expand Up @@ -154,37 +178,48 @@ export class ConfiguratorSelect extends LitElement {
* Renders a single {@link ConfiguratorSelectOption}.
*
* @param option The {@link ConfiguratorSelectOption} to render.
* @param nested Whether the option to be rendered is a member of an opt
* group.
* @returns
*/
protected renderOption(option: ConfiguratorSelectOption): TemplateResult {
protected renderOption(
option: ConfiguratorSelectOption,
nested = false,
): TemplateResult {
if (this.isOptGroup(option)) {
// MdSelect doesn't support opt groups. Emulate this behavior by rendering
// a disabled option, then manually indenting all nested options.
return html`
<optgroup label="${option.label}">
${option.options.map(opt => this.renderOption(opt))}
</optgroup>
<md-select-option disabled>
<div slot="headline">${option.label}</div>
</md-select-option>
${option.options.map(opt => this.renderOption(opt, true))}
`;
}

return html` <option
return html`<md-select-option
?disabled="${option.disabled}"
?selected="${option.selected}"
value="${ifDefined(option.value)}"
>
${option.label}
</option>`;
<div slot="headline">
${when(nested, () =>
Array<TemplateResult>(OPT_GROUP_INDENT).fill(html`&nbsp;`),
)}${option.label}
</div>
</md-select-option>`;
}

render() {
return html`${when(
this.label,
() => html`<label for="${this.id}">${this.label}</label>`,
)}
<select
id="${this.id}"
name="${ifDefined(this.name)}"
@input=${this.handleInput}
>
${this.internalOptions.map(option => this.renderOption(option))}
</select>`;
return html`<md-filled-select
id="${this.id}"
label="${ifDefined(this.label)}"
name="${ifDefined(this.name)}"
?quick="${!MENU_ANIMATION_ENABLED}"
.displayText="${this.selectedOptions[0].label}"
@input=${this.handleInput}
>
${this.internalOptions.map(option => this.renderOption(option))}
</md-filled-select>`;
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
97 changes: 97 additions & 0 deletions test/web/fixtures/configurator-expect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {expect, Locator} from '@playwright/test';

/**
* Custom assertions for working with configurator elements in tests.
*/
export const configuratorExpect = expect.extend({
/**
* Ensures the {@link Locator} points to an enabled element.
*
* @param locator
* @returns
*/
async toBeEnabled(locator: Locator) {
try {
const tag = await locator.evaluate(elem => elem.tagName);
await (tag.toLowerCase() === 'md-select-option'
? expect(locator).not.toHaveAttribute('disabled', /.*/)
: expect(locator).toBeEnabled());

return {
pass: true,
message: () => 'Expected element to be disabled, but it was enabled.',
};
} catch (e) {
return {
pass: false,
message: () => 'Expected element to be enabled, but it was disabled.',
};
}
},

/**
* Ensures the {@link Locator} points to a disabled element.
*
* @param locator
* @returns
*/
async toBeDisabled(locator: Locator) {
try {
const tag = await locator.evaluate(elem => elem.tagName);
await (tag.toLowerCase() === 'md-select-option'
? expect(locator).toHaveAttribute('disabled', /.*/)
: expect(locator).toBeDisabled());

return {
pass: true,
message: () => 'Expected element to be enabled, but it was disabled.',
};
} catch (e) {
return {
pass: false,
message: () => 'Expected element to be disabled, but it was enabled.',
};
}
},

/**
* Ensures the {@link Locator} points to a selected option element.
*
* @param locator
* @returns
*/
async toBeSelected(locator: Locator) {
try {
expect((await locator.evaluate(elem => elem.tagName)).toLowerCase()).toBe(
'md-select-option',
);
await expect(locator).toHaveAttribute('data-aria-selected', 'true');

return {
pass: true,
message: () => 'Expected option to be unselected, but it was selected.',
};
} catch (e) {
return {
pass: false,
message: () => 'Expected option to be selected, but it was unselected.',
};
}
},
});
50 changes: 42 additions & 8 deletions test/web/fixtures/configurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,14 @@
* limitations under the License.
*/

import {
expect as baseExpect,
Locator,
mergeExpects,
Page,
test as baseTest,
} from '@playwright/test';
import {Locator, mergeExpects, Page, test as baseTest} from '@playwright/test';

import {SampleConfig} from '../../../src/model/sample-config.js';
import {encode} from '../../../src/util/base64url.js';

export const expect = baseExpect;
import {configuratorExpect} from './configurator-expect.js';

export const expect = mergeExpects(configuratorExpect);

export const test = baseTest.extend<{
config: SampleConfig;
Expand Down Expand Up @@ -72,4 +68,42 @@ export class Configurator {
.locator(`configurator-checkbox[label="${label}"]`)
.locator('input');
}

/**
* Returns configurator select element(s).
*/
getSelect(label: string, parent: Locator = this.page.locator('body')) {
return parent.locator(`md-filled-select[label="${label}"]`);
}

/**
* Returns configurator select option(s).
*/
getSelectOption(selectElem: Locator, optionText: string) {
return selectElem.locator('md-select-option').filter({hasText: optionText});
}

/**
* Selects the specified option of the specified configurator select.
*
* This method asserts that the provided {@link Locator} points to a
* valid configurator select element, and that the specified option
* was actually selected.
*/
async selectOption(selectElem: Locator, optionText: string) {
// Ensure the provided selector actually points to a select element.
expect(
(await selectElem.evaluate(elem => elem.tagName)).toLowerCase(),
).toBe('md-filled-select');

// Open the select.
await selectElem.click();

// Find the specified option and try to click it.
const option = this.getSelectOption(selectElem, optionText);
await option.click();

// Ensure the option was actually selected.
await expect(option).toBeSelected();
}
}
Loading

0 comments on commit 28ecf8a

Please sign in to comment.