Skip to content

Commit

Permalink
feat(select): add form integration
Browse files Browse the repository at this point in the history
  • Loading branch information
dpellier committed Feb 5, 2024
1 parent 8d7c4df commit 62fa493
Show file tree
Hide file tree
Showing 19 changed files with 93 additions and 296 deletions.
133 changes: 10 additions & 123 deletions packages/components/src/components.d.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { OdsValidityState } from '@ovhcloud/ods-common-core';
import type { EventEmitter } from '@stencil/core';

interface OdsInputValueChangeEventDetail {
name?: string,
validity: OdsValidityState;
value: string | undefined | null;
oldValue?: string | undefined | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class OsdsInput implements OdsInputAttribute, OdsInputEvent, OdsInputMeth

emitChange(value: OdsInputValue, oldValue?: OdsInputValue) {
this.odsValueChange.emit({
name: this.name,
value: value == null ? value : `${value}`,
oldValue: oldValue == null ? oldValue : `${oldValue}`,
validity: this.controller.getInputValidity(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,6 @@ export class OsdsRange implements OdsRangeAttribute, OdsRangeEvent {
this.controller.onKeyup(event, inputEl, dual);
}

handleClick(): void {
this.controller.handleClick();
}

hasError(): boolean {
return this.controller.hasError();
}
Expand All @@ -115,13 +111,11 @@ export class OsdsRange implements OdsRangeAttribute, OdsRangeEvent {
}

return (
<Host {...{
onclick: this.handleClick.bind(this),
}}
class={{
'ods-error': this.hasError(),
'dual-range': this.controller.isDualRange(),
}}
<Host
class={{
'ods-error': this.hasError(),
'dual-range': this.controller.isDualRange(),
}}
>
<span {...{
class: 'range-bounds',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import type { OsdsSelect } from '../osds-select/osds-select';
import type { HTMLStencilElement } from '@stencil/core/internal';

import { Component, Element, Host, State, h } from '@stencil/core';

import { DEFAULT_ATTRIBUTE } from '../osds-select/constants/default-attributes';


/**
* @slot (unnamed) - Select group content
*/
@Component({
tag: 'osds-select-group',
styleUrl: 'osds-select-group.scss',
Expand All @@ -19,10 +13,6 @@ export class OsdsSelectGroup {

@Element() el!: HTMLStencilElement;

/**
* The size of the select option
* @internal
*/
@State() size = DEFAULT_ATTRIBUTE.size;

componentWillLoad() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,10 @@ import type { OdsSelectOptionMethod } from './interfaces/methods';
import type { OsdsSelect } from '../osds-select/osds-select';
import type { OdsInputValue } from '@ovhcloud/ods-common-core';
import type { HTMLStencilElement } from '@stencil/core/internal';

import { Component, Element, Event, EventEmitter, Host, Method, Prop, State, h } from '@stencil/core';

import { DEFAULT_ATTRIBUTE } from './constants/default-attributes';
import { DEFAULT_ATTRIBUTE as SELECT_DEFAULT_ATTRIBUTE } from '../osds-select/constants/default-attributes';


/**
* @slot (unnamed) - Select option content
*/
@Component({
tag: 'osds-select-option',
styleUrl: 'osds-select-option.scss',
Expand All @@ -30,12 +24,10 @@ export class OsdsSelectOption implements OdsSelectOptionAttribute, OdsSelectOpti
* @internal
*/
@Prop({ reflect: true }) selected?: boolean = false;
@Prop({ reflect: true }) value: OdsInputValue = DEFAULT_ATTRIBUTE.value;

/**
* The size of the select option
* @internal
*/
@State() size = SELECT_DEFAULT_ATTRIBUTE.size;
@State() tabindex = 0;

componentWillLoad() {
this.selectParent = this.el.closest('osds-select') as (HTMLStencilElement & OsdsSelect) | null;
Expand All @@ -44,16 +36,6 @@ export class OsdsSelectOption implements OdsSelectOptionAttribute, OdsSelectOpti
}
}

/**
* The tabindex of the select option
* @internal
*/
@State() tabindex = 0;

/** @see OdsSelectOptionAttributes.value */
@Prop({ reflect: true }) value: OdsInputValue = DEFAULT_ATTRIBUTE.value;

/** @see OdsSelectOptionEvents.odsSelectOptionClickEventDetail */
@Event() odsSelectOptionClick!: EventEmitter<OdsSelectOptionClickEventDetail>;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const DEFAULT_ATTRIBUTE: OdsSelectAttribute = Object.freeze({
disabled: false,
error: false,
inline: false,
name: undefined,
opened: false,
required: false,
size: ODS_SELECT_SIZE.md,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import type { OsdsSelectOption } from '../../osds-select-option/osds-select-option';
import type { OcdkSurface } from '@ovhcloud/ods-cdk';

import { OcdkSurfaceMock } from '@ovhcloud/ods-cdk';

import { OdsSelectController } from './controller';
import { OsdsSelect } from '../osds-select';


class OdsSelectMock extends OsdsSelect {
class OdsSelectMock {
constructor(attribute: Partial<OsdsSelect>) {
super();
Object.assign(this, attribute);
}

surface = new OcdkSurfaceMock() as unknown as OcdkSurface;
handleSelectClick = jest.fn();
handleValueChange = jest.fn();
setFocus = jest.fn();

internals = {
setFormValue: jest.fn()
};
}

describe('spec:ods-select-controller', () => {
Expand All @@ -26,7 +26,7 @@ describe('spec:ods-select-controller', () => {
let item2: OsdsSelectOption & HTMLElement;

function setup(attributes: Partial<OsdsSelect> = {}) {
component = new OdsSelectMock(attributes);
component = new OdsSelectMock(attributes) as unknown as OsdsSelect;

if (component.surface) {
component.surface.opened = attributes?.opened ?? false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@

import type { OdsSelectOptionClickEventDetail, OsdsSelectOption } from '../../osds-select-option/public-api';
import type { OsdsSelect } from '../osds-select';
import type { OdsValidityState } from '@ovhcloud/ods-common-core';
import type { OdsInputValue, OdsValidityState } from '@ovhcloud/ods-common-core';

/**
* common controller logic for select component used by the different implementations.
* it contains all the glue between framework implementation and the third party service.
*/
class OdsSelectController {
private component: OsdsSelect;
private _selectOptions: (HTMLElement & OsdsSelectOption)[] = [];
Expand All @@ -23,6 +18,20 @@ class OdsSelectController {
this.component = component;
}

beforeInit(): void {
if (this.component.value === '' && this.component.defaultValue !== undefined) {
this.component.value = this.component.defaultValue;
}
this.component.internals.setFormValue(this.component.value?.toString() ?? '');
this.component.openedChanged(this.component.opened);
this.component.selectedLabelSlot = this.component.el.querySelector('[slot="selectedLabel"]');
}

changeValue(value: OdsInputValue) {
this.component.value = value;
this.component.internals.setFormValue(value?.toString() ?? '');
}

/**
* get the validity object properties of the component.
* it is based on the validity state of the vanilla select.
Expand Down Expand Up @@ -152,6 +161,11 @@ class OdsSelectController {
}));
this.component.setFocus();
}

onValueChange(value: OdsInputValue, oldValue?: OdsInputValue): void {
this.component.internals.setFormValue(value?.toString() ?? '');
this.component.emitChange(value, oldValue);
}
}

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ interface OdsSelectAttribute {
error?: boolean;
/** full width or not: see component principles */
inline: boolean;
/** name of the select field */
name?: string;
/** opened or not */
opened?: boolean;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { OsdsSelectOption } from '../../osds-select-option/osds-select-opti
import type { OdsInputValue, OdsValidityState } from '@ovhcloud/ods-common-core';
import type { EventEmitter } from '@stencil/core';


interface OdsSelectValueChangeEventDetail {
name?: string,
oldValue?: OdsInputValue,
selection: OsdsSelectOption | null,
validity: OdsValidityState,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { E2EPage } from '@stencil/core/testing';

import { odsComponentAttributes2StringAttributes, odsStringAttributes2Str } from '@ovhcloud/ods-common-testing';
import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import { E2EElement, newE2EPage } from '@stencil/core/testing';

import { DEFAULT_ATTRIBUTE } from './constants/default-attributes';
import { ODS_SELECT_SIZE } from './constants/select-size';
import { OdsSelectAttribute } from './interfaces/attributes';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import type { OdsSelectAttribute } from './interfaces/attributes';
import type { OdsSelectValueChangeEventDetail } from './interfaces/events';
import type { E2EElement, E2EPage } from '@stencil/core/testing';

import { odsComponentAttributes2StringAttributes, odsStringAttributes2Str } from '@ovhcloud/ods-common-testing';
import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import { newE2EPage } from '@stencil/core/testing';

import { DEFAULT_ATTRIBUTE } from './constants/default-attributes';
import { ODS_SELECT_SIZE } from './constants/select-size';



describe('e2e:osds-select', () => {
const baseAttribute = { ariaLabel: null, ariaLabelledby: '', color: ODS_THEME_COLOR_INTENT.primary, defaultValue: '', disabled: false, inline: false, required: false, size: ODS_SELECT_SIZE.md, value: '' };
let page: E2EPage;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
jest.mock('./core/controller'); // keep jest.mock before any

import type { OdsSelectAttribute } from './interfaces/attributes';
import type { SpecPage } from '@stencil/core/testing';

import { OdsLogger } from '@ovhcloud/ods-common-core';
import { OdsMockNativeMethod, OdsMockPropertyDescriptor, odsComponentAttributes2StringAttributes, odsStringAttributes2Str, odsUnitTestAttribute } from '@ovhcloud/ods-common-testing';
import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import { newSpecPage } from '@stencil/core/testing';

import { DEFAULT_ATTRIBUTE } from './constants/default-attributes';
import { DEFAULT_VALIDITY_STATE } from './constants/default-validity-state';
import { ODS_SELECT_SIZE } from './constants/select-size';
import { OsdsSelect } from './osds-select';


const mutationObserverMock = jest.fn(function MutationObserver(callback) {
this.observe = jest.fn();
this.disconnect = jest.fn();
Expand All @@ -23,13 +21,10 @@ const mutationObserverMock = jest.fn(function MutationObserver(callback) {
// @ts-ignore
global.MutationObserver = mutationObserverMock;

const logger = new OdsLogger('osds-select-spec');

// mock validity property that does not exist when stencil mock HTMLInputElement
OdsMockPropertyDescriptor(HTMLInputElement.prototype, 'validity', () => DEFAULT_VALIDITY_STATE);

describe('spec:osds-select', () => {
logger.log('init');
const baseAttribute = { ariaLabel: null, ariaLabelledby: '', color: ODS_THEME_COLOR_INTENT.primary, defaultValue: '', disabled: false, inline: false, required: false, size: ODS_SELECT_SIZE.md, value: '' };
let page: SpecPage;
let instance: OsdsSelect;
Expand Down Expand Up @@ -166,13 +161,6 @@ describe('spec:osds-select', () => {
});

describe('methods', () => {
it('should have defaultValue as value if set', async() => {
const defaultValue = 4;
await setup({ attributes: { defaultValue } });
expect(instance).toBeTruthy();
expect(instance?.value).toBe(`${defaultValue}`);
});

it('should call reset function and set value to defaultValue', async() => {
const defaultValue = 4;
await setup({ attributes: { defaultValue, value: 2 } });
Expand Down
Loading

0 comments on commit 62fa493

Please sign in to comment.