Skip to content

Commit

Permalink
feat(input): add tests about odsChange emit on first render
Browse files Browse the repository at this point in the history
  • Loading branch information
dpellier committed Nov 28, 2024
1 parent d566284 commit af559b0
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import { ODS_BUTTON_COLOR, ODS_BUTTON_SIZE, ODS_BUTTON_VARIANT } from '../../../
import { ODS_ICON_NAME } from '../../../../icon/src';
import { ODS_SPINNER_COLOR } from '../../../../spinner/src';
import { ODS_INPUT_TYPE, type OdsInputType } from '../../constants/input-type';
import { handleKeySpaceEnter, isPassword, updateInternals } from '../../controller/ods-input';
import { VALUE_DEFAULT_VALUE, getInitialValue, handleKeySpaceEnter, isPassword, updateInternals } from '../../controller/ods-input';
import { type OdsInputChangeEventDetail } from '../../interfaces/events';

const VALUE_DEFAULT_VALUE = null;

@Component({
formAssociated: true,
shadow: {
Expand Down Expand Up @@ -165,9 +163,7 @@ export class OdsInput {

// We set the value before the observer starts to avoid calling the mutation callback twice
// as it will be called on componentDidLoad (when native element validity is up-to-date)
if (!this.value && this.value !== 0 && (this.value !== VALUE_DEFAULT_VALUE || this.defaultValue)) {
this.value = this.defaultValue ?? null;
}
this.value = getInitialValue(this.value, this.defaultValue);
}

componentDidLoad(): void {
Expand Down
12 changes: 12 additions & 0 deletions packages/ods/src/components/input/src/controller/ods-input.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { setInternalsValidityFromHtmlElement } from '../../../../utils/dom';

const VALUE_DEFAULT_VALUE = null;

function getInitialValue(value: string | number | null, defaultValue?: string | number): string | number | null {
if (defaultValue !== undefined && value === VALUE_DEFAULT_VALUE) {
return defaultValue;
}

return value;
}

async function handleKeySpaceEnter(event: KeyboardEvent, isDisabled: boolean, callback: () => Promise<void>): Promise<void> {
event.preventDefault();
event.stopPropagation();
Expand All @@ -21,7 +31,9 @@ function updateInternals(internals: ElementInternals, value: number | string | n
}

export {
getInitialValue,
handleKeySpaceEnter,
isPassword,
updateInternals,
VALUE_DEFAULT_VALUE,
};
133 changes: 131 additions & 2 deletions packages/ods/src/components/input/tests/behaviour/ods-input.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { E2EElement, E2EPage } from '@stencil/core/testing';
import { newE2EPage } from '@stencil/core/testing';
import { type E2EElement, type E2EPage, newE2EPage } from '@stencil/core/testing';
import { type OdsInputChangeEventDetail } from '../../src';

describe('ods-input behaviour', () => {
let el: E2EElement;
Expand All @@ -19,6 +19,135 @@ describe('ods-input behaviour', () => {

beforeEach(jest.clearAllMocks);

describe('initialization', () => {
let odsChangeEventCount = 0;
let odsChangeEventDetail = {};

async function setupWithSpy(content: string): Promise<void> {
odsChangeEventCount = 0;
odsChangeEventDetail = {};
page = await newE2EPage();

// page.spyOnEvent doesn't seems to work to observe event emitted on first render, before any action happens
// so we spy manually
await page.exposeFunction('onOdsChangeEvent', (detail: OdsInputChangeEventDetail) => {
odsChangeEventCount++;
odsChangeEventDetail = detail;
});

await page.evaluateOnNewDocument(() => {
window.addEventListener('odsChange', (event: Event) => {
// @ts-ignore function is exposed manually
window.onOdsChangeEvent((event as CustomEvent<OdsInputChangeEventDetail>).detail);
});
});

await page.setContent(content);
}

describe('with no value attribute defined', () => {
it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: null,
});
});
});

describe('with empty string value', () => {
it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input value=""></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: '',
});
});
});

describe('with no value but empty default-value', () => {
it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input default-value=""></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: '',
});
});
});

describe('with no value but default-value defined', () => {
it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input default-value="default"></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: 'default',
});
});

it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input default-value="33"></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: '33',
});
});
});

describe('with defined value', () => {
it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input value="value"></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: 'value',
});
});

it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input value="42"></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: '42',
});
});
});

describe('with defined value and default value', () => {
it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input default-value="default" value="value"></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: 'value',
});
});

it('should trigger a uniq odsChange event', async() => {
await setupWithSpy('<ods-input default-value="33" value="42"></ods-input>');

expect(odsChangeEventCount).toBe(1);
expect(odsChangeEventDetail).toEqual({
validity: {},
value: '42',
});
});
});
});

describe('methods', () => {
describe('clear', () => {
it('should receive odsClear event', async() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,41 @@
<<<<<<< HEAD
import { handleKeySpaceEnter, isPassword } from '../../src/controller/ods-input';
=======
jest.mock('../../../../utils/dom');

describe('ods-input controller', () => {
import { setInternalsValidityFromHtmlElement } from '../../../../utils/dom';
import { getInitialValue, handleKeySpace, isPassword, updateInternals } from '../../src/controller/ods-input';
>>>>>>> abdf93bc2 (feat(input): add tests about odsChange emit on first render)

describe('ods-input controller', () => {
beforeEach(jest.clearAllMocks);

describe('getInitialValue', () => {
it('should return null if value is null and no default value', () => {
expect(getInitialValue(null)).toBe(null);
});

it('should return string or number if value is set regarding of default value', () => {
expect(getInitialValue('')).toBe('');
expect(getInitialValue(0)).toBe(0);
expect(getInitialValue('value')).toBe('value');
expect(getInitialValue(42)).toBe(42);
expect(getInitialValue('value', 'default')).toBe('value');
expect(getInitialValue(42, 33)).toBe(42);
// @ts-ignore for test purpose
expect(getInitialValue('value', null)).toBe('value');
// @ts-ignore for test purpose
expect(getInitialValue(42, null)).toBe(42);
});

it('should return default value if value is null', () => {
expect(getInitialValue(null, '')).toBe('');
expect(getInitialValue(null, 0)).toBe(0);
expect(getInitialValue(null, 'default')).toBe('default');
expect(getInitialValue(null, 33)).toBe(33);
});
});

describe('isPassword', () => {
it('should return true if not undefined', async() => {
expect(isPassword(undefined)).toBe(false);
Expand Down Expand Up @@ -57,4 +89,44 @@ describe('ods-input controller', () => {
expect(callback).not.toHaveBeenCalled();
});
});

describe('updateInternals', () => {
const dummyInput = { dummy: 'input' };
const dummyInternal = {
setFormValue: jest.fn(),
} as unknown as ElementInternals;

it('should set internal value with empty string', () => {
// @ts-ignore for test purpose
updateInternals(dummyInternal);
expect(dummyInternal.setFormValue).toHaveBeenCalledWith('');

// @ts-ignore for test purpose
updateInternals(dummyInternal, undefined, {} as HTMLInputElement);
expect(dummyInternal.setFormValue).toHaveBeenCalledWith('');

updateInternals(dummyInternal, null, {} as HTMLInputElement);
expect(dummyInternal.setFormValue).toHaveBeenCalledWith('');
});

it('should set internal value with string value', () => {
const dummyValue = 'dummy value';

updateInternals(dummyInternal, dummyValue, {} as HTMLInputElement);

expect(dummyInternal.setFormValue).toHaveBeenCalledWith(dummyValue);
});

it('should not set internal validity if no input element is defined', () => {
updateInternals(dummyInternal, 'dummyValue');

expect(setInternalsValidityFromHtmlElement).not.toHaveBeenCalled();
});

it('should set internal validity if input element is defined', () => {
updateInternals(dummyInternal, 'dummyValue', dummyInput as unknown as HTMLInputElement);

expect(setInternalsValidityFromHtmlElement).toHaveBeenCalledWith(dummyInput, dummyInternal);
});
});
});

0 comments on commit af559b0

Please sign in to comment.