feat: Implement EncryptionService and StringService with comprehensive tests
This commit is contained in:
@@ -67,17 +67,17 @@ const INDONESIA_TZ_MAP: Record<string, string> = {
|
|||||||
* Fluent date manipulation interface.
|
* Fluent date manipulation interface.
|
||||||
* All methods are immutable and return a new instance.
|
* All methods are immutable and return a new instance.
|
||||||
*/
|
*/
|
||||||
interface IDateManager {
|
interface IDateService {
|
||||||
format(format?: string): string;
|
format(format?: string): string;
|
||||||
add(value: number, unit: TimeUnit): IDateManager;
|
add(value: number, unit: TimeUnit): IDateService;
|
||||||
subtract(value: number, unit: TimeUnit): IDateManager;
|
subtract(value: number, unit: TimeUnit): IDateService;
|
||||||
isBefore(date: DateInput | DateService): boolean;
|
isBefore(date: DateInput | DateService): boolean;
|
||||||
isAfter(date: DateInput | DateService): boolean;
|
isAfter(date: DateInput | DateService): boolean;
|
||||||
isSame(date: DateInput | DateService, unit?: TimeUnit): boolean;
|
isSame(date: DateInput | DateService, unit?: TimeUnit): boolean;
|
||||||
diff(date: DateInput | DateService, unit: TimeUnit, precise?: boolean): number;
|
diff(date: DateInput | DateService, unit: TimeUnit, precise?: boolean): number;
|
||||||
diffCalendarDay(date: DateInput | DateService): number;
|
diffCalendarDay(date: DateInput | DateService): number;
|
||||||
startOf(unit: TimeUnit): IDateManager;
|
startOf(unit: TimeUnit): IDateService;
|
||||||
endOf(unit: TimeUnit): IDateManager;
|
endOf(unit: TimeUnit): IDateService;
|
||||||
toISOString(): string;
|
toISOString(): string;
|
||||||
toDate(): Date;
|
toDate(): Date;
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ interface IDateManager {
|
|||||||
* All instances are automatically normalized
|
* All instances are automatically normalized
|
||||||
* to a single global timezone.
|
* to a single global timezone.
|
||||||
*/
|
*/
|
||||||
export class DateService implements IDateManager {
|
export class DateService implements IDateService {
|
||||||
private readonly _date: Dayjs;
|
private readonly _date: Dayjs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -298,8 +298,6 @@ export class DateService implements IDateManager {
|
|||||||
* Timezone Utilities
|
* Timezone Utilities
|
||||||
* ---------------------------------------------------------------- */
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
// ... (method getSupportedTimezones dan get timezoneAbbr yang sudah ada)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns timezone abbreviation combined with GMT offset.
|
* Returns timezone abbreviation combined with GMT offset.
|
||||||
* Useful for UI labels.
|
* Useful for UI labels.
|
||||||
|
|||||||
+2
@@ -13,6 +13,7 @@ describe('EncryptionService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Reset the Singleton instance to ensure clean state for every test
|
// Reset the Singleton instance to ensure clean state for every test
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(EncryptionService as any).instance = undefined;
|
(EncryptionService as any).instance = undefined;
|
||||||
service = new EncryptionService(TEST_KEY);
|
service = new EncryptionService(TEST_KEY);
|
||||||
});
|
});
|
||||||
@@ -82,6 +83,7 @@ describe('EncryptionService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty string if input is malformed (Graceful Fail)', () => {
|
it('should return an empty string if input is malformed (Graceful Fail)', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
const malformedData = 'invalid-base64-string-@@@';
|
const malformedData = 'invalid-base64-string-@@@';
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './encryption/encryption-key';
|
export * from './encryption-service/encryption-key';
|
||||||
export * from './encryption/encryption-service';
|
export * from './encryption-service/encryption-service';
|
||||||
|
|
||||||
export * from './date-service/date-service';
|
export * from './date-service/date-service';
|
||||||
export * from './currency-service/currency-service';
|
export * from './currency-service/currency-service';
|
||||||
|
export * from './string-service/string-service';
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { StringService } from './string-service';
|
||||||
|
|
||||||
|
describe('StringService', () => {
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Instantiation & Static Methods
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
describe('Initialization', () => {
|
||||||
|
it('should handle string input correctly', () => {
|
||||||
|
const svc = new StringService('Hello');
|
||||||
|
expect(svc.value()).toBe('Hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle number input by converting to string', () => {
|
||||||
|
const svc = new StringService(123);
|
||||||
|
expect(svc.value()).toBe('123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null input gracefully (default to empty string)', () => {
|
||||||
|
const svc = new StringService(null);
|
||||||
|
expect(svc.value()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined input gracefully', () => {
|
||||||
|
const svc = new StringService(undefined);
|
||||||
|
expect(svc.value()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support static factory method .of()', () => {
|
||||||
|
const svc = StringService.of('Factory');
|
||||||
|
expect(svc.value()).toBe('Factory');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('random()', () => {
|
||||||
|
it('should generate string with specified length', () => {
|
||||||
|
const random = StringService.random(15);
|
||||||
|
expect(random.value()).toHaveLength(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate alphanumeric characters only', () => {
|
||||||
|
const random = StringService.random(100);
|
||||||
|
expect(random.value()).toMatch(/^[A-Za-z0-9]+$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Core Manipulations
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
describe('Transformations', () => {
|
||||||
|
it('should convert to upperCase', () => {
|
||||||
|
expect(StringService.of('hello').upperCase().value()).toBe('HELLO');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert to lowerCase', () => {
|
||||||
|
expect(StringService.of('HELLO').lowerCase().value()).toBe('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trim whitespace', () => {
|
||||||
|
expect(StringService.of(' hello ').trim().value()).toBe('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support method chaining (Immutable)', () => {
|
||||||
|
const original = StringService.of(' hello ');
|
||||||
|
const modified = original.trim().upperCase();
|
||||||
|
|
||||||
|
expect(original.value()).toBe(' hello '); // Original untouched
|
||||||
|
expect(modified.value()).toBe('HELLO'); // New instance modified
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Capitalization', () => {
|
||||||
|
it('should capitalize first letter only', () => {
|
||||||
|
expect(StringService.of('hELLO world').capitalizeFirst().value()).toBe('Hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should capitalize each word', () => {
|
||||||
|
expect(StringService.of('hello world').capitalizeEachWord().value()).toBe('Hello World');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle double spaces in capitalizeEachWord', () => {
|
||||||
|
// Test regex logic for splitting words
|
||||||
|
expect(StringService.of('hello world').capitalizeEachWord().value()).toBe('Hello World');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Utilities (Slug, Limit, Mask)
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
describe('slugify()', () => {
|
||||||
|
it('should create valid slugs', () => {
|
||||||
|
expect(StringService.of('Hello World!').slugify().value()).toBe('hello-world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex characters', () => {
|
||||||
|
expect(StringService.of('C# & .NET Core').slugify().value()).toBe('c-net-core');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove leading/trailing separators', () => {
|
||||||
|
expect(StringService.of('---Hello---').slugify().value()).toBe('hello');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('limit()', () => {
|
||||||
|
it('should truncate string if longer than max', () => {
|
||||||
|
expect(StringService.of('Hello World').limit(5).value()).toBe('Hello...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not truncate if shorter than max', () => {
|
||||||
|
expect(StringService.of('Hi').limit(5).value()).toBe('Hi');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support custom suffix', () => {
|
||||||
|
expect(StringService.of('Hello World').limit(5, '!!!').value()).toBe('Hello!!!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mask()', () => {
|
||||||
|
it('should mask middle characters', () => {
|
||||||
|
// 0812 3456 7890 (Length 12)
|
||||||
|
// Start 4: 0812
|
||||||
|
// End 3: 890
|
||||||
|
// Middle masked: *****
|
||||||
|
expect(StringService.of('081234567890').mask(4, 3).value()).toBe('0812*****890');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle custom mask char', () => {
|
||||||
|
expect(StringService.of('123456').mask(2, 2, 'X').value()).toBe('12XX56');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return original if string is shorter than visible parts', () => {
|
||||||
|
expect(StringService.of('123').mask(5, 5).value()).toBe('123');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Inspection & Outputs
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
describe('Inspection & Outputs', () => {
|
||||||
|
it('should detect empty strings correctly', () => {
|
||||||
|
expect(StringService.of('').isEmpty()).toBe(true);
|
||||||
|
expect(StringService.of(' ').isEmpty()).toBe(true); // Trim check
|
||||||
|
expect(StringService.of(null).isEmpty()).toBe(true);
|
||||||
|
expect(StringService.of('a').isEmpty()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return default value with orElse', () => {
|
||||||
|
expect(StringService.of(null).orElse('Default')).toBe('Default');
|
||||||
|
expect(StringService.of('Valid').orElse('Default')).toBe('Valid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support native string interpolation (toString)', () => {
|
||||||
|
const name = StringService.of('World');
|
||||||
|
expect(`Hello ${name}`).toBe('Hello World');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support JSON serialization (toJSON)', () => {
|
||||||
|
const data = {
|
||||||
|
id: 1,
|
||||||
|
name: StringService.of('Product A'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// JSON.stringify automatically calls .toJSON()
|
||||||
|
const json = JSON.stringify(data);
|
||||||
|
expect(json).toBe('{"id":1,"name":"Product A"}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
/**
|
||||||
|
* ------------------------------------------------------------
|
||||||
|
* StringService
|
||||||
|
* ------------------------------------------------------------
|
||||||
|
* Centralized string manipulation utility.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - 🛡️ Null-safe (handles null/undefined gracefully)
|
||||||
|
* - ⛓️ Fluent and immutable API (chainable methods)
|
||||||
|
* - 🛠️ Common formatting utilities (Capitalization, Truncation)
|
||||||
|
* - 🎲 Generator utilities (Random string, Slugs)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic Chaining
|
||||||
|
* StringService.of(" hello world ")
|
||||||
|
* .trim()
|
||||||
|
* .capitalizeEachWord()
|
||||||
|
* .value(); // "Hello World"
|
||||||
|
* ------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------
|
||||||
|
* Types
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
export type StringInput = string | number | null | undefined;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------
|
||||||
|
* Interfaces
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
interface IStringService {
|
||||||
|
// --- Output & Conversion ---
|
||||||
|
value(): string;
|
||||||
|
toString(): string;
|
||||||
|
toJSON(): string;
|
||||||
|
|
||||||
|
// --- Manipulations (Chainable) ---
|
||||||
|
upperCase(): IStringService;
|
||||||
|
lowerCase(): IStringService;
|
||||||
|
capitalizeFirst(): IStringService;
|
||||||
|
capitalizeEachWord(): IStringService;
|
||||||
|
limit(maxLength: number, suffix?: string): IStringService;
|
||||||
|
trim(): IStringService;
|
||||||
|
slugify(): IStringService;
|
||||||
|
mask(visibleStart: number, visibleEnd: number, maskChar?: string): IStringService;
|
||||||
|
|
||||||
|
// --- Inspection & Fallback ---
|
||||||
|
isEmpty(): boolean;
|
||||||
|
orElse(defaultValue: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------
|
||||||
|
* StringService
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
export class StringService implements IStringService {
|
||||||
|
private readonly _value: string;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Constructor & Static Initializers
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
constructor(value?: StringInput) {
|
||||||
|
// Defensive coding: Convert null/undefined/number to string immediately
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
this._value = '';
|
||||||
|
} else {
|
||||||
|
this._value = String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static factory method for cleaner chaining.
|
||||||
|
*
|
||||||
|
* @param value - The input string, number, or null/undefined.
|
||||||
|
* @example
|
||||||
|
* StringService.of("hello").upperCase().value()
|
||||||
|
*/
|
||||||
|
static of(value?: StringInput): StringService {
|
||||||
|
return new StringService(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random alphanumeric string.
|
||||||
|
* Useful for IDs, tokens, or temporary passwords.
|
||||||
|
*
|
||||||
|
* @param length - Length of the generated string (default: 10).
|
||||||
|
* @example
|
||||||
|
* StringService.random(8).value() // "aB9x2Z1m"
|
||||||
|
*/
|
||||||
|
static random(length: number = 10): StringService {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
return new StringService(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Core Manipulations
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts string to UPPERCASE.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* StringService.of("hello").upperCase().value() // "HELLO"
|
||||||
|
*/
|
||||||
|
upperCase(): StringService {
|
||||||
|
return new StringService(this._value.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts string to lowercase.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* StringService.of("HELLO").lowerCase().value() // "hello"
|
||||||
|
*/
|
||||||
|
lowerCase(): StringService {
|
||||||
|
return new StringService(this._value.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalizes only the first letter of the entire string.
|
||||||
|
* Remainder is forced to lowercase (Sentence case).
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* StringService.of("HELLO world").capitalizeFirst().value() // "Hello world"
|
||||||
|
*/
|
||||||
|
capitalizeFirst(): StringService {
|
||||||
|
if (!this._value) return this;
|
||||||
|
const lower = this._value.toLowerCase();
|
||||||
|
return new StringService(lower.charAt(0).toUpperCase() + lower.slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalizes the first letter of every word (Title Case).
|
||||||
|
* Automatically handles multiple spaces.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* StringService.of("hello world").capitalizeEachWord().value() // "Hello World"
|
||||||
|
* StringService.of("hello world").capitalizeEachWord().value() // "Hello World"
|
||||||
|
*/
|
||||||
|
capitalizeEachWord(): StringService {
|
||||||
|
if (!this._value) return this;
|
||||||
|
const transformed = this._value
|
||||||
|
.toLowerCase()
|
||||||
|
.split(/\s+/) // Split by any whitespace regex to handle double spaces
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ');
|
||||||
|
return new StringService(transformed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncates string to a specific length and adds a suffix.
|
||||||
|
*
|
||||||
|
* @param maxLength - The character limit.
|
||||||
|
* @param suffix - The string to append if truncated (default: "...").
|
||||||
|
* @example
|
||||||
|
* StringService.of("Lorem Ipsum").limit(5).value() // "Lorem..."
|
||||||
|
* StringService.of("Lorem Ipsum").limit(5, "").value() // "Lorem"
|
||||||
|
*/
|
||||||
|
limit(maxLength: number, suffix: string = '...'): StringService {
|
||||||
|
if (this._value.length <= maxLength) return this;
|
||||||
|
return new StringService(this._value.substring(0, maxLength) + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Utility Extensions
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes whitespace from both ends of the string.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* StringService.of(" data ").trim().value() // "data"
|
||||||
|
*/
|
||||||
|
trim(): StringService {
|
||||||
|
return new StringService(this._value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts string to URL-friendly slug.
|
||||||
|
* Removes special characters and replaces spaces with dashes.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* StringService.of("Hello World!").slugify().value() // "hello-world"
|
||||||
|
* StringService.of("C# & .NET").slugify().value() // "c-net"
|
||||||
|
*/
|
||||||
|
slugify(): StringService {
|
||||||
|
const slug = this._value
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^\w\s-]/g, '') // Remove non-word chars
|
||||||
|
.replace(/[\s_-]+/g, '-') // Replace spaces and underscores with -
|
||||||
|
.replace(/^-+|-+$/g, ''); // Remove leading/trailing -
|
||||||
|
return new StringService(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Masks part of the string. Useful for PII (emails/phones).
|
||||||
|
*
|
||||||
|
* @param visibleStart - Number of characters to show at the start.
|
||||||
|
* @param visibleEnd - Number of characters to show at the end.
|
||||||
|
* @param maskChar - The character to use for masking (default: "*").
|
||||||
|
* @example
|
||||||
|
* StringService.of("08123456789").mask(4, 3).value() // "0812****789"
|
||||||
|
*/
|
||||||
|
mask(visibleStart: number = 0, visibleEnd: number = 0, maskChar: string = '*'): StringService {
|
||||||
|
if (this._value.length <= visibleStart + visibleEnd) return this;
|
||||||
|
|
||||||
|
const start = this._value.slice(0, visibleStart);
|
||||||
|
const end = this._value.slice(-visibleEnd);
|
||||||
|
const middle = maskChar.repeat(this._value.length - visibleStart - visibleEnd);
|
||||||
|
|
||||||
|
return new StringService(start + middle + end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------
|
||||||
|
* Inspection & Output
|
||||||
|
* ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the string is empty or contains only whitespace.
|
||||||
|
*
|
||||||
|
* @returns true if string is empty or whitespace-only.
|
||||||
|
* @example
|
||||||
|
* StringService.of("").isEmpty() // true
|
||||||
|
* StringService.of(" ").isEmpty() // true
|
||||||
|
*/
|
||||||
|
isEmpty(): boolean {
|
||||||
|
return this._value.trim().length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw string value.
|
||||||
|
*/
|
||||||
|
value(): string {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns default value if current string is empty.
|
||||||
|
*
|
||||||
|
* @param defaultValue - The fallback string.
|
||||||
|
* @example
|
||||||
|
* StringService.of(null).orElse("N/A") // "N/A"
|
||||||
|
*/
|
||||||
|
orElse(defaultValue: string): string {
|
||||||
|
return this.isEmpty() ? defaultValue : this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows default JS string interpolation to work.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const name = StringService.of("John");
|
||||||
|
* console.log(`Hello ${name}`); // "Hello John"
|
||||||
|
*/
|
||||||
|
toString(): string {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows JSON.stringify to serialize just the string, not the object wrapper.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* JSON.stringify({ name: StringService.of("John") }) // '{"name":"John"}'
|
||||||
|
*/
|
||||||
|
toJSON(): string {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user