Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mes-9511 character count of input fields #1805

Merged
merged 4 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 66 additions & 29 deletions src/directives/__tests__/character-count.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// eslint-disable-next-line max-classes-per-file
import { Component, DebugElement, ElementRef } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
Expand All @@ -11,7 +10,6 @@ class TestCharCountComponent {}

class ElementRefMock extends ElementRef {
nativeElement = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getAttribute(qualifiedName: string): string | null {
return '10';
},
Expand All @@ -37,48 +35,87 @@ describe('Directive: CharacterCountDirective', () => {
expect(directiveEl).not.toBeNull();
});

describe('onIonChange', () => {
it('should emit onCharacterCountChanged if ' + 'charLimit is true and e.value is not undefined', () => {
describe('ngAfterViewInit', () => {
it('should emit onCharacterCountChanged if charLimit is true', () => {
directiveInstance['charLimit'] = 3;
directiveInstance.onIonChange({ value: '11' });
directiveInstance.el.nativeElement.value = '11';
directiveInstance.ngAfterViewInit();
expect(directiveInstance.onCharacterCountChanged.emit).toHaveBeenCalledWith(1);
});
it('should return undefined if charLimit is false and e.value is not undefined', () => {
directiveInstance['charLimit'] = null;
directiveInstance.onIonChange({ value: '11' });
expect(directiveInstance.onCharacterCountChanged.emit).not.toHaveBeenCalled();
});

describe('onInput', () => {
it('should call handleChange with the correct value', () => {
spyOn(directiveInstance, 'handleChange');
directiveInstance.onInput({ target: { value: 'hello' } });
expect(directiveInstance.handleChange).toHaveBeenCalledWith('hello');
});
it('should return undefined if charLimit is true and e.value is undefined', () => {
directiveInstance['charLimit'] = 3;
directiveInstance.onIonChange({ value: undefined });
expect(directiveInstance.onCharacterCountChanged.emit).not.toHaveBeenCalled();
});

describe('onIonChange', () => {
it('should call handleChange with the correct value', () => {
spyOn(directiveInstance, 'handleChange');
directiveInstance.onIonChange({ target: { value: 'hello' } });
expect(directiveInstance.handleChange).toHaveBeenCalledWith('hello');
});
});

describe('onInput', () => {
it('should emit onCharacterCountChanged if ' + 'charLimit is true and e.target.value is not undefined', () => {
directiveInstance['charLimit'] = 3;
directiveInstance.onInput({ target: { value: '11' } });
expect(directiveInstance.onCharacterCountChanged.emit).toHaveBeenCalledWith(1);
describe('handleChange', () => {
it('should emit remaining character count when charLimit is set and value is provided', () => {
directiveInstance['charLimit'] = 10;
directiveInstance.handleChange('hello');
expect(directiveInstance.onCharacterCountChanged.emit).toHaveBeenCalledWith(5);
});
it('should return undefined if charLimit is false and e.target.value is not undefined', () => {

it('should not emit when charLimit is not set', () => {
directiveInstance['charLimit'] = null;
directiveInstance.onInput({ target: { value: '11' } });
directiveInstance.handleChange('hello');
expect(directiveInstance.onCharacterCountChanged.emit).not.toHaveBeenCalled();
});
it('should return undefined if charLimit is true and e.target.value is undefined', () => {
directiveInstance['charLimit'] = 3;
directiveInstance.onInput({ target: { value: undefined } });

it('should not emit when value is undefined', () => {
directiveInstance['charLimit'] = 10;
directiveInstance.handleChange(undefined);
expect(directiveInstance.onCharacterCountChanged.emit).not.toHaveBeenCalled();
});

it('should emit remaining character count when input is empty string', () => {
directiveInstance['charLimit'] = 10;
directiveInstance.handleChange('');
expect(directiveInstance.onCharacterCountChanged.emit).toHaveBeenCalledWith(10);
});

it('should emit remaining character count when input is null', () => {
directiveInstance['charLimit'] = 10;
directiveInstance.handleChange(null);
expect(directiveInstance.onCharacterCountChanged.emit).toHaveBeenCalledWith(10);
});
});

describe('ngAfterViewInit', () => {
it('should emit onCharacterCountChanged if charLimit is true', () => {
directiveInstance['charLimit'] = 3;
directiveInstance.el.nativeElement.value = '11';
directiveInstance.ngAfterViewInit();
expect(directiveInstance.onCharacterCountChanged.emit).toHaveBeenCalledWith(1);
describe('getUtf8ByteLength', () => {
it('should return correct byte length for ASCII characters', () => {
const result = directiveInstance.getUtf8ByteLength('hello');
expect(result).toBe(5);
});

it('should return correct byte length for multi-byte characters', () => {
const result = directiveInstance.getUtf8ByteLength('你好');
expect(result).toBe(6);
});

it('should return 0 for an empty string', () => {
const result = directiveInstance.getUtf8ByteLength('');
expect(result).toBe(0);
});

it('should return correct byte length for mixed characters', () => {
const result = directiveInstance.getUtf8ByteLength('hello你好');
expect(result).toBe(11);
});

it('returns correct byte length for special characters', () => {
const result = directiveInstance.getUtf8ByteLength('!@#$%^&*()');
expect(result).toBe(10);
});
});
});
65 changes: 57 additions & 8 deletions src/directives/character-count.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,69 @@ export class CharacterCountDirective implements AfterViewInit {
this.charLimit = this.el.nativeElement.getAttribute('charLimit');
}

/**
* Lifecycle hook that is called after a component's view has been fully initialized.
*
* This method calculates the remaining character count based on the initial value
* of the input field and emits the result through the `onCharacterCountChanged` event.
*/
ngAfterViewInit() {
const valueLength = this.el.nativeElement.value ? this.el.nativeElement.value.length : 0;
const valueLength = this.el.nativeElement.value || '';
const byteLength = this.getUtf8ByteLength(valueLength);
if (this.charLimit) {
this.onCharacterCountChanged.emit(this.charLimit - valueLength);
this.onCharacterCountChanged.emit(this.charLimit - byteLength);
}
}

onInput(e: any) {
if (!this.charLimit || e.target.value === undefined) return;
this.onCharacterCountChanged.emit(this.charLimit - e.target.value.length);
/**
* Handles the input event for the input field.
*
* This method is triggered when the input event occurs on the input field.
* It delegates the handling of the event to the handleChange method.
*
* @param event - The event object containing the new value of the input field.
*/
onInput(event: any) {
this.handleChange(event.target.value);
}

onIonChange(e: any) {
if (!this.charLimit || e.value === undefined) return;
this.onCharacterCountChanged.emit(this.charLimit - e.value.length);
/**
* Handles the ionChange event for the input field.
*
* This method is triggered when the ionChange event occurs on the input field.
* It delegates the handling of the event to the handleChange method.
*
* @param event - The event object containing the new value of the input field.
*/
onIonChange(event: any) {
this.handleChange(event.target.value);
}

/**
* Handles the change event for the input field.
*
* This method calculates the remaining character count based on the UTF-8 byte length
* of the input value and emits the result through the `onCharacterCountChanged` event.
*
* @param value - The current value of the input field.
*/
handleChange(value: string) {
if (this.charLimit !== null && value !== undefined) {
const byteLength = this.getUtf8ByteLength(value);
this.onCharacterCountChanged.emit(this.charLimit - byteLength);
}
}

/**
* Get the byte length of a string
*
* @param input - The string to calculate the byte length of
* @returns The byte length of the input
*
*/
getUtf8ByteLength(input: string): number {
if (input === null) return 0;
// Using TextEncoder to get the byte length
return new TextEncoder().encode(input).length;
}
}
Loading