Skip to content

Commit

Permalink
fix: component reset & clear event order + focus & null value
Browse files Browse the repository at this point in the history
  • Loading branch information
aesteves60 authored and dpellier committed Jul 29, 2024
1 parent 8a307b4 commit b23f705
Show file tree
Hide file tree
Showing 19 changed files with 131 additions and 53 deletions.
8 changes: 4 additions & 4 deletions packages/examples/react-webpack/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { type ReactElement } from 'react';
import { Gallery } from './components/gallery/Gallery';
// import { Gallery } from './components/gallery/Gallery';
// import { FormFormik } from './components/formFormik/FormFormik';
// import { FormNative } from './components/formNative/FormNative';
import styles from './app.scss';
import { FormNative } from './components/formNative/FormNative';

function App(): ReactElement {
return (
<div className={ styles.app }>
<Gallery />
{/* <Gallery /> */}
{/*<FormFormik />*/}
{/*<FormNative />*/}
<FormNative />
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import React, { type ReactElement, useRef, useState } from 'react';
import styles from './formNative.scss';

function FormNative(): ReactElement {
// const checkboxRef = useRef<HTMLOdsCheckboxElement>(null);
const checkboxRef = useRef<HTMLOdsCheckboxElement>(null);
const datepickerRef = useRef<HTMLOdsDatepickerElement>(null);
const formRef = useRef<HTMLFormElement>(null);
const inputNumberRef = useRef<HTMLOdsInputElement>(null);
const inputTextRef = useRef<HTMLOdsInputElement>(null);
const passwordRef = useRef<HTMLOdsPasswordElement>(null);
const phoneNumberRef = useRef<HTMLOdsPhoneNumberElement>(null);
const quantityRef = useRef<HTMLOdsQuantityElement>(null);
// const radioRef = useRef<HTMLOdsRadioElement>(null);
const radioRef = useRef<HTMLOdsRadioElement>(null);
const selectRef = useRef<HTMLOdsSelectElement>(null);
const switchRef = useRef<HTMLOdsSwitchElement>(null);
const textareaRef = useRef<HTMLOdsTextareaElement>(null);
Expand Down Expand Up @@ -48,21 +48,21 @@ function FormNative(): ReactElement {
// await validateField(selectRef.current);
// await validateField(switchRef.current);
// await validateField(textareaRef.current);
await validateField(timepickerRef.current);
// await validateField(timepickerRef.current);

const formData = new FormData(formRef.current!);
console.log(formData)
console.log(formData);

return false;
}

async function validateField(element: any) {
async function validateField(element: any): Promise<void> {
if (!element) {
return;
}

const validity = await element.getValidity();
// console.log(validity)
// console.log(validity)
if (validity !== undefined) {
setError({
...error,
Expand All @@ -71,6 +71,18 @@ function FormNative(): ReactElement {
}
}

function onChange(event: CustomEvent) {
console.log('onChange', event);
}

function onReset(event: CustomEvent): void {
console.log('onReset', event);
}

function onClear(event: CustomEvent): void {
console.log('onClear', event);
}

return (
<form
className={ styles['form-native'] }
Expand All @@ -84,6 +96,7 @@ function FormNative(): ReactElement {
inputId="checkbox"
name="checkbox"
value="checkbox"
ref={ checkboxRef }
/>
<label htmlFor="checkbox">Checked</label>

Expand Down Expand Up @@ -113,21 +126,20 @@ function FormNative(): ReactElement {
defaultValue={ 23 }
hasError={ error.inputNumber }
isClearable={ true }
isRequired={ true }
// isRequired={ true }
name="inputNumber"
onOdsChange={ () => validateField(inputNumberRef.current) }
ref={ inputNumberRef }
type={ ODS_INPUT_TYPE.number }
/>

{/* OK */}
<OdsInput
defaultValue="input text"
// defaultValue="input text"
hasError={ error.inputText }
isClearable={ true }
isRequired={ true }
// isRequired={ true }

name="inputText"
onOdsChange={ () => validateField(inputTextRef.current) }
ref={ inputTextRef }
type={ ODS_INPUT_TYPE.text }
/>
Expand All @@ -139,30 +151,27 @@ function FormNative(): ReactElement {
isClearable={ true }
isRequired={ true }
name="password"
onOdsChange={ () => validateField(passwordRef.current) }
ref={ passwordRef }
/>

{/* KO default value not in formData on immediate submit */}
{/* KO style width different */}
<OdsPhoneNumber
defaultValue="+33123456789"
// defaultValue="+33123456789"
hasError={ error.phoneNumber }
isClearable={ true }
isRequired={ true }
isoCode="fr"
name="phoneNumber"
onOdsChange={ () => validateField(phoneNumberRef.current) }
ref={ phoneNumberRef }
/>

{/* KO reset does not reset formData if no default value */}
<OdsQuantity
defaultValue={ 22 }
// defaultValue={ 22 }
hasError={ error.quantity }
isRequired={ true }
name="quantity"
onOdsChange={ () => validateField(quantityRef.current) }
ref={ quantityRef }
/>

Expand All @@ -172,7 +181,10 @@ function FormNative(): ReactElement {
// isRequired={ true }
inputId="radio1"
name="radio"
isChecked
value="radio-1"
ref={ radioRef }

/>
<label htmlFor="radio1">Radio 1</label>

Expand All @@ -192,6 +204,9 @@ function FormNative(): ReactElement {
hasError={ error.select }
isRequired={ true }
name="select"
onOdsChange={ (event) => onChange(event) }
onOdsReset={ (event) => onReset(event) }
onOdsClear={ (event) => onClear(event) }
ref={ selectRef }
>
<option value="dog">Dog</option>
Expand All @@ -202,14 +217,18 @@ function FormNative(): ReactElement {
<option value="goldfish">Goldfish</option>
</OdsSelect>

<button onClick={ () => checkboxRef.current?.clear() }>Clear</button>

{/* KO isRequired => no way to get validity */}
{/* KO should have an hasError status */}
{/* Two event change on reset & clear */}
<OdsSwitch
isRequired={ true }
name="switch"
ref={ switchRef }
>
<OdsSwitchItem
isChecked
name="switch"
value="option1"
>
Expand All @@ -229,18 +248,16 @@ function FormNative(): ReactElement {
hasError={ error.textarea }
isRequired={ true }
name="textarea"
onOdsChange={ () => validateField(textareaRef.current) }
ref={ textareaRef }
/>

{/* KO value not in formData when no default */}
{/* KO value in formData not updated when default */}
<OdsTimepicker
defaultValue="12:34"
// defaultValue="12:34"
hasError={ error.timepicker }
isRequired={ true }
name="timepicker"
onOdsChange={ () => validateField(timepickerRef.current) }
ref={ timepickerRef }
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class OdsCheckbox {
}
this.odsClear.emit();
hasChange && this.onInput();
this.inputEl?.focus();
}

@Method()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export class OdsInput {

@Method()
async clear(): Promise<void> {
this.odsClear.emit();
this.value = null;
this.inputEl?.focus();
this.odsClear.emit();
}

@Method()
Expand All @@ -69,8 +69,8 @@ export class OdsInput {

@Method()
async reset(): Promise<void> {
this.value = this.defaultValue ?? null;
this.odsReset.emit();
this.value = this.defaultValue ?? null;
}

@Watch('isMasked')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export class OdsQuantity {
private onOdsChange(event: OdsInputValueChangeEvent): void {
if (event.detail.value === null) {
this.value = null;
return;
} else {
this.value = Number(event.detail.value) ?? null;
}
this.value = Number(event.detail.value) ?? null;
setFormValue(this.internals, this.value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ interface OdsQuantityEventValueChangeDetail {
name: string;
previousValue?: number;
validity?: ValidityState;
value: number;
value: number | null;
}

type OdsQuantityEventValueChange = CustomEvent<OdsQuantityEventValueChangeDetail>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ describe('ods-quantity behaviour', () => {
await submitButton.click();
await page.waitForNetworkIdle();
const url = new URL(page.url());
expect(url.searchParams.get('ods-quantity')).toBe('0');
expect(url.searchParams.get('ods-quantity')).toBe('');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export class OdsRadio {
this.inputEl.checked = false;
}
this.odsClear.emit();
this.emitChange({
checked: false,
name: this.name,
validity: this.inputEl?.validity,
value: this.value ?? null,
});
this.inputEl?.focus();
}

@Method()
Expand All @@ -47,13 +54,19 @@ export class OdsRadio {
if (!inputRadio) {
return;
}
if (radio.getAttribute('is-checked') === '') {
if (radio.getAttribute('is-checked') !== null) {
inputRadio.checked = true;
} else {
inputRadio.checked = false;
}
});
this.odsReset.emit();
this.emitChange({
checked: this.isChecked,
name: this.name,
validity: this.inputEl?.validity,
value: this.value ?? null,
});
}

@Method()
Expand All @@ -65,15 +78,24 @@ export class OdsRadio {
return document.querySelectorAll(`ods-radio[name="${this.name}"]`);
}

private onInput(event: InputEvent): void {
this.odsChange.emit({
private onInput(event: Event): void {
this.emitChange({
checked: (event.target as HTMLInputElement)?.checked,
name: this.name,
validity: this.inputEl?.validity,
value: this.value ?? null,
});
}

private emitChange(detail: OdsRadioValueChangeEventDetail): void {
this.odsChange.emit({
checked: detail.checked,
name: detail.name,
validity: detail.validity,
value: detail.value,
});
}

render(): FunctionalComponent {
return (
<Host class="ods-radio">
Expand Down
29 changes: 27 additions & 2 deletions packages/ods/src/components/radio/tests/behaviour/ods-radio.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,43 @@ describe('ods-radio behaviour', () => {
describe('Methods', () => {
describe('method:clear', () => {
it('should receive odsClear event', async() => {
await setup('<ods-radio value="value" is-checked></ods-radio>');
await setup('<ods-radio name="ods-radio" value="value" is-checked></ods-radio>');
const odsClearSpy = await page.spyOnEvent('odsClear');
const odsChangeSpy = await page.spyOnEvent('odsChange');
expect(await isInputRadioChecked(el)).toBe(true);
await el.callMethod('clear');
await page.waitForChanges();

expect(await isInputRadioChecked(el)).toBe(false);
expect(odsClearSpy).toHaveReceivedEventTimes(1);
expect(odsChangeSpy).toHaveReceivedEventTimes(1);
expect(odsChangeSpy).toHaveReceivedEventDetail({
checked: false,
name: 'ods-radio',
validity: {},
value: 'value',
});
});
});

describe('method:reset', () => {
it('should receive odsReset event', async() => {
await setup('<ods-radio value="value" is-checked></ods-radio>');
await setup('<ods-radio name="ods-radio" value="value" is-checked></ods-radio>');
const odsResetSpy = await page.spyOnEvent('odsReset');
const odsChangeSpy = await page.spyOnEvent('odsChange');
expect(await isInputRadioChecked(el)).toBe(true);
await el.callMethod('reset');
await page.waitForChanges();

expect(await isInputRadioChecked(el)).toBe(true);
expect(odsResetSpy).toHaveReceivedEventTimes(1);
expect(odsChangeSpy).toHaveReceivedEventTimes(1);
expect(odsChangeSpy).toHaveReceivedEventDetail({
checked: true,
name: 'ods-radio',
validity: {},
value: 'value',
});
});

it('should checked the radio with is-checked after reset', async() => {
Expand All @@ -55,6 +71,8 @@ describe('ods-radio behaviour', () => {
<ods-radio name="radio-group" value="value3"></ods-radio>`);
const radios = await page.findAll('ods-radio');
const odsResetSpy = await page.spyOnEvent('odsReset');
const odsChangeSpy = await page.spyOnEvent('odsChange');

expect(await isInputRadioChecked(radios[0])).toBe(true);

await radios[2].click();
Expand All @@ -64,6 +82,13 @@ describe('ods-radio behaviour', () => {

expect(await isInputRadioChecked(radios[0])).toBe(true);
expect(odsResetSpy).toHaveReceivedEventTimes(1);
expect(odsChangeSpy).toHaveReceivedEventTimes(2);
expect(odsChangeSpy).toHaveReceivedEventDetail({
checked: false,
name: 'radio-group',
validity: {},
value: 'value3',
});
});
});

Expand Down
Loading

0 comments on commit b23f705

Please sign in to comment.