Skip to content

Commit

Permalink
feat(textarea): 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 761607a commit d566284
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { AttachInternals, Component, Element, Event, type EventEmitter, type FunctionalComponent, Host, Listen, Method, Prop, State, h } from '@stencil/core';
import { updateInternals } from '../../controller/ods-textarea';
import { VALUE_DEFAULT_VALUE, getInitialValue, updateInternals } from '../../controller/ods-textarea';
import { type OdsTextareaChangeEventDetail } from '../../interfaces/events';

const VALUE_DEFAULT_VALUE = null;

@Component({
formAssociated: true,
shadow: {
Expand Down Expand Up @@ -119,9 +117,7 @@ export class OdsTextarea {

// 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 !== VALUE_DEFAULT_VALUE || this.defaultValue)) {
this.value = this.defaultValue ?? null;
}
this.value = getInitialValue(this.value, this.defaultValue);
}

componentDidLoad(): void {
Expand Down
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 | null, defaultValue?: string): string | null {
if (defaultValue !== undefined && value === VALUE_DEFAULT_VALUE) {
return defaultValue;
}

return value;
}

function updateInternals(internals: ElementInternals, value: string | null, textareaElement?: HTMLTextAreaElement): void {
internals.setFormValue(value ?? '');

Expand All @@ -9,5 +19,7 @@ function updateInternals(internals: ElementInternals, value: string | null, text
}

export {
getInitialValue,
updateInternals,
VALUE_DEFAULT_VALUE,
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type E2EElement, type E2EPage, newE2EPage } from '@stencil/core/testing';
import { type OdsTextareaChangeEventDetail } from '../../src';

describe('ods-textarea behaviour', () => {
let el: E2EElement;
Expand All @@ -9,7 +10,7 @@ describe('ods-textarea behaviour', () => {
page = await newE2EPage();

await page.setContent(content);
await page.evaluate(() => document.body.style.setProperty('margin', '0px'));
await page.evaluate(() => document.body.style.setProperty('margin', '0'));

if (customStyle) {
await page.addStyleTag({ content: customStyle });
Expand All @@ -21,6 +22,105 @@ describe('ods-textarea 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: OdsTextareaChangeEventDetail) => {
odsChangeEventCount++;
odsChangeEventDetail = detail;
});

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

await page.setContent(content);
}

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

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-textarea value=""></ods-textarea>');

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-textarea default-value=""></ods-textarea>');

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-textarea default-value="default"></ods-textarea>');

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

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

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

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

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

describe('methods', () => {
describe('clear', () => {
it('should emit an odsClear event', async() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
jest.mock('../../../../utils/dom');

import { setInternalsValidityFromHtmlElement } from '../../../../utils/dom';
import { getInitialValue, updateInternals } from '../../src/controller/ods-textarea';

describe('ods-textarea 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 if value is set regarding of default value', () => {
expect(getInitialValue('')).toBe('');
expect(getInitialValue('value')).toBe('value');
expect(getInitialValue('value', 'default')).toBe('value');
// @ts-ignore for test purpose
expect(getInitialValue('value', null)).toBe('value');
});

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

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

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 HTMLTextAreaElement);
expect(dummyInternal.setFormValue).toHaveBeenCalledWith('');

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

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

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

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

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

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

it('should set internal validity if textarea element is defined', () => {
updateInternals(dummyInternal, 'dummyValue', dummyTextarea as unknown as HTMLTextAreaElement);

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

0 comments on commit d566284

Please sign in to comment.