Skip to content

Commit

Permalink
feat: Added text area component
Browse files Browse the repository at this point in the history
Closes #764
  • Loading branch information
rkaraivanov committed May 26, 2023
1 parent dbb12ea commit 2a6fa11
Show file tree
Hide file tree
Showing 7 changed files with 646 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/components/common/definitions/defineAllComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import IgcCircularGradientComponent from '../../progress/circular-gradient.js';
import IgcDateTimeInputComponent from '../../date-time-input/date-time-input.js';
import IgcMaskInputComponent from '../../mask-input/mask-input.js';
import IgcExpansionPanelComponent from '../../expansion-panel/expansion-panel.js';
import IgcTextareaComponent from '../../textarea/textarea.js';
import IgcTreeComponent from '../../tree/tree.js';
import IgcTreeItemComponent from '../../tree/tree-item.js';
import IgcStepperComponent from '../../stepper/stepper.js';
Expand Down Expand Up @@ -115,6 +116,7 @@ const allComponents: CustomElementConstructor[] = [
IgcDateTimeInputComponent,
IgcStepperComponent,
IgcStepComponent,
IgcTextareaComponent,
];

export const defineAllComponents = () => {
Expand Down
10 changes: 10 additions & 0 deletions src/components/common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,13 @@ export function createCounter() {
export function isLTR(element: HTMLElement) {
return getComputedStyle(element).getPropertyValue('direction') === 'ltr';
}

export function extractText<T extends Node>(arr: T[]) {
return arr.reduce((agg: string[], item: T) => {
const text = item.textContent?.trim();
if (text) {
agg.push(text);
}
return agg;
}, []);
}
6 changes: 2 additions & 4 deletions src/components/select/select-item.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { queryAssignedNodes } from 'lit/decorators.js';
import { watch } from '../common/decorators/watch.js';
import IgcDropdownItemComponent from '../dropdown/dropdown-item.js';
import { extractText } from '../common/util.js';

/**
* Represents an item in a select list.
Expand Down Expand Up @@ -37,10 +38,7 @@ export default class IgcSelectItemComponent extends IgcDropdownItemComponent {

/** Returns the text of the item without the prefix and suffix content. */
public override get textContent() {
return this.content
.map((t) => t.textContent?.trim())
.filter((t) => t !== '')
.join(' ');
return extractText(this.content).join(' ');
}

/** Sets the textContent of the item without touching the prefix and suffix content. */
Expand Down
136 changes: 136 additions & 0 deletions src/components/textarea/textarea.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
import sinon from 'sinon';
import { defineComponents } from '../common/definitions/defineComponents.js';
import IgcTextareaComponent from './textarea.js';

describe('Textarea component', () => {
before(() => defineComponents(IgcTextareaComponent));

let element: IgcTextareaComponent;
let textArea: HTMLTextAreaElement;

describe('Setting value through attribute and projection', () => {
const value = 'Hello world!';

it('through attribute', async () => {
element = await fixture<IgcTextareaComponent>(
html`<igc-textarea value=${value}></igc-textarea>`
);
expect(element.value).to.equal(value);
});

it('through slot projection', async () => {
element = await fixture<IgcTextareaComponent>(
html`<igc-textarea>${value}</igc-textarea>`
);
expect(element.value).to.equal(value);
});

it('priority of slot over attribute value binding', async () => {
element = await fixture<IgcTextareaComponent>(
html`<igc-textarea value="ignored">${value}</igc-textarea>`
);

expect(element.value).to.equal(value);
});

it('reflects on slotchange state', async () => {
const additional = ['...', 'Goodbye world!'];

element = await fixture<IgcTextareaComponent>(
html`<igc-textarea>${value}</igc-textarea>`
);

element.append(...additional);
await elementUpdated(element);

expect(element.value).to.equal([value, ...additional].join('\r\n'));
});
});

describe('Events', () => {
beforeEach(async () => {
element = await fixture<IgcTextareaComponent>(
html`<igc-textarea></igc-textarea>`
);
textArea = element.shadowRoot!.querySelector('textarea')!;
});

it('igcInput', async () => {
const spy = sinon.spy(element, 'emitEvent');

textArea.value = '123';
textArea.dispatchEvent(new Event('input'));

await elementUpdated(element);

expect(spy).calledOnceWithExactly('igcInput', { detail: '123' });
expect(element.value).to.equal(textArea.value);
});

it('igcChange', async () => {
const spy = sinon.spy(element, 'emitEvent');

textArea.value = '20230317';
textArea.dispatchEvent(new Event('change'));

expect(spy).calledOnceWithExactly('igcChange', { detail: '20230317' });
expect(element.value).to.equal(textArea.value);
});
});

describe('Methods API', () => {
const projected = 'Hello world!';

beforeEach(async () => {
element = await fixture<IgcTextareaComponent>(
html`<igc-textarea>${projected}</igc-textarea>`
);
textArea = element.shadowRoot!.querySelector('textarea')!;
});

it('select()', async () => {
element.select();
const [start, end] = [textArea.selectionStart, textArea.selectionEnd];

expect([start, end]).to.eql([0, projected.length]);
});

it('setSelectionRange()', async () => {
element.setSelectionRange(0, 'Hello'.length);
const [start, end] = [textArea.selectionStart, textArea.selectionEnd];

expect([start, end]).to.eql([0, 'Hello'.length]);
});

it('setRangeText()', async () => {
element.setRangeText('Goodbye', 0, 'Hello'.length, 'select');
const [start, end] = [textArea.selectionStart, textArea.selectionEnd];

expect([start, end]).to.eql([0, 'Goodbye'.length]);
expect(element.value).to.equal('Goodbye world!');
});

it('focus()', async () => {
const spy = sinon.spy(element, 'emitEvent');

element.focus();
expect(textArea.matches(':focus')).to.be.true;
expect(spy).calledOnceWithExactly('igcFocus');
});

it('blur()', async () => {
element.focus();

const spy = sinon.spy(element, 'emitEvent');

element.blur();
expect(textArea.matches(':focus')).to.be.false;
expect(spy).calledOnceWithExactly('igcBlur');
});
});

describe('Form integration', () => {
// Once #726 is merged
});
});
Loading

0 comments on commit 2a6fa11

Please sign in to comment.