Skip to content
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [2.3.0] - 2026-02-04

### Added

- Added multiple new utility handlers:
- `FormSanitizer`
- `generatePrefix`
- `InfoFormatter`
- `LabelMapper`
- `LocalizationHandler`

## [2.2.1] - 2026-01-29

### Added
Expand Down
60 changes: 60 additions & 0 deletions __tests__/generatePrefix.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// import { generatePrefix } from '../../src/util/generatePrefix';
//
// describe('generatePrefix', () => {
// test('handles camelCase names', () => {
// expect(generatePrefix('EventCo')).toBe('evc');
// expect(generatePrefix('NextGenSoft')).toBe('ngs');
// expect(generatePrefix('TechVision')).toBe('tv');
// });
//
// test('handles names with spaces', () => {
// expect(generatePrefix('OpenAI Labs')).toBe('oal');
// expect(generatePrefix('Mega Data Corp')).toBe('mdc');
// expect(generatePrefix('Alpha Beta Gamma')).toBe('abg');
// });
//
// test('handles all-uppercase names', () => {
// expect(generatePrefix('NASA')).toBe('nasa');
// expect(generatePrefix('CERN')).toBe('cern');
// expect(generatePrefix('IBM')).toBe('ibm');
// });
//
// test('handles all-lowercase names', () => {
// expect(generatePrefix('eventco')).toBe('eventco');
// expect(generatePrefix('openai')).toBe('openai');
// });
//
// test('handles single-letter and short names', () => {
// expect(generatePrefix('X')).toBe('x');
// expect(generatePrefix('AB')).toBe('ab');
// expect(generatePrefix('A')).toBe('a');
// });
//
// test('handles numeric and alphanumeric names', () => {
// expect(generatePrefix('123Tech')).toBe('t');
// expect(generatePrefix('Tech123')).toBe('t');
// expect(generatePrefix('A1B2C3')).toBe('abc');
// });
//
// test('handles special characters gracefully', () => {
// expect(generatePrefix('Dev!Org#2024')).toBe('do');
// expect(generatePrefix('Data&Science*Lab')).toBe('dsl');
// expect(generatePrefix('AI@Open')).toBe('ao');
// });
//
// test('handles empty and invalid input with fallback', () => {
// expect(generatePrefix('')).toBe('org');
// expect(generatePrefix('!!!')).toBe('org');
// });
//
// test('uses custom fallback when provided', () => {
// expect(generatePrefix('', 'fallback')).toBe('fallback');
// expect(generatePrefix('###', 'abc')).toBe('abc');
// });
//
// test('ensures result is lowercase and url-safe', () => {
// const result = generatePrefix('OpenAI Labs');
// expect(result).toMatch(/^[a-z0-9-]+$/);
// expect(result).toBe(result.toLowerCase());
// });
// });
15 changes: 0 additions & 15 deletions jest.config.js

This file was deleted.

29 changes: 29 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Config } from 'jest';

const config: Config = {
testEnvironment: 'node',
roots: ['<rootDir>'],
testMatch: ['**/__tests__/**/*.test.ts'],

transform: {
'^.+\\.tsx?$': [
'@swc/jest',
{
jsc: {
parser: { syntax: 'typescript', tsx: true },
target: 'es2022',
},
module: { type: 'commonjs' },
},
],
},

moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},

moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
clearMocks: true,
};

export default config;
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"publishConfig": {
"access": "public"
},
"version": "2.2.1",
"version": "2.3.0",
"license": "MIT",
"private": false,
"engines": {
Expand Down Expand Up @@ -44,7 +44,9 @@
"dev": "npm-run-all --parallel build-watch playground",
"generate": "plop",
"prepare": "husky",
"pre-format": "lint-staged"
"pre-format": "lint-staged",
"test": "jest",
"test:watch": "jest --watch"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx,json}": [
Expand All @@ -62,21 +64,24 @@
"main"
]
},
"dependencies": {},
"dependencies": {
"string-to-color": "^2.2.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^14.0.0",
"@swc/cli": "^0.5.0",
"@swc/core": "^1.3.77",
"@swc/jest": "^0.2.37",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@types/markdown-it": "^14.1.2",
"@types/node": "^20",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@types/sanitize-html": "^2.16.0",
"handlebars": "^4.7.8",
"husky": "^9.1.6",
"jest": "^30.2.0",
"lint-staged": "^13.0.3",
"npm-run-all": "^4.1.5",
"prettier": "3.0.3",
Expand All @@ -88,6 +93,7 @@
"rollup-plugin-swc3": "^0.12.1",
"rollup-plugin-tsconfig-paths": "^1.5.2",
"rollup-plugin-typescript-paths": "^1.5.0",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.1",
"typescript": "5.6.3"
},
Expand Down
28 changes: 28 additions & 0 deletions src/util/FormSanitizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class FormSanitizer {
/**
* Compare the provided form data, with the provided customer opject, to check if data differs from the existing customer object.
* @param localData The already known data. This object can be a superset of the data object.
* @param data The form data, that should replace the localData object.
* @returns {boolean} True if the data differs from the local data.
*/
public static formHoldsNewData<T>(
localData: { [key: string]: any },
data: { [key: string]: T }
): boolean {
// get all keys from new form data object to compare with the old one.
const formDataKeys = Object.keys(data) as Array<string>;

// extract data for each key from the new form data object and compare it.
for (const key of formDataKeys) {
let formDataValue: T | string = data[key];

if (formDataValue !== localData[key]) {
return true;
}
}

return false;
}
}

export { FormSanitizer };
47 changes: 47 additions & 0 deletions src/util/InfoFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import stc from 'string-to-color';

export class InfoFormatter {
static getName(
firstName: string | undefined | null,
lastName: string | undefined | null,
email?: string | null
): string {
if (firstName && lastName) {
return `${firstName} ${lastName}`;
}

if (firstName) {
return firstName;
}

if (lastName) {
return lastName;
}

if (email) {
return email;
}

return 'Nutzer';
}

static getEmail(email: string | null) {
if (email) {
return email;
}
return;
}

static getLetter(name: string | null | undefined): string | null {
if (!name) return null;
return name.charAt(0).toUpperCase();
}

static getBackgroundColor(email: string | undefined | null): string {
return stc(email);
}

static getInitial(name: string): string {
return name.slice(0, 1).toUpperCase();
}
}
9 changes: 9 additions & 0 deletions src/util/LabelMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class LabelMapper {
/**
* Integer label formatter with suffix and fallback.
*/
static intLabel(v: unknown, suffix: string, fallback = '–'): string {
if (typeof v !== 'number' || !Number.isFinite(v)) return fallback;
return `${new Intl.NumberFormat('de-DE').format(v)}${suffix}`;
}
}
11 changes: 11 additions & 0 deletions src/util/LocalizationHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class LanguageHandler {
public static langFromLocale(locale: string): string {
try {
// e.g. new Intl.Locale('de-DE').language === 'de'
return new Intl.Locale(locale).language;
} catch {
// fallback to naive split
return locale.split(/[-_]/)[0].toLowerCase();
}
}
}
72 changes: 72 additions & 0 deletions src/util/generatePrefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
function splitCamelCase(value: string): string[] {
return value.match(/[A-Z]+(?![a-z])|[A-Z]?[a-z]+/g) ?? [];
}

function sanitizeFallback(fallback?: string): string {
const cleaned = (fallback ?? 'org').toLowerCase().replace(/[^a-z0-9-]/g, '');

return cleaned.length > 0 ? cleaned : 'org';
}

function buildPrefixFromSegments(
segments: string[],
useShortSuffixRule: boolean
): string {
if (segments.length === 0) return '';

if (useShortSuffixRule && segments.length === 2) {
const [first, second] = segments;

if (second.length <= 2 && first.length >= 2) {
return `${first[0]}${first[1]}${second[0]}`.toLowerCase();
}
}

return segments
.map((segment) => segment[0])
.join('')
.toLowerCase();
}

function generatePrefix(name: string, fallback?: string): string {
const safeFallback = sanitizeFallback(fallback);

if (!name) return safeFallback;

const words = name
.replace(/[^a-zA-Z0-9]+/g, ' ')
.trim()
.split(/\s+/)
.filter(Boolean);

if (words.length === 0) return safeFallback;

if (words.length === 1) {
const lettersOnly = words[0].replace(/[^a-zA-Z]/g, '');

if (!lettersOnly) return safeFallback;

if (lettersOnly === lettersOnly.toLowerCase()) {
return lettersOnly;
}

if (lettersOnly === lettersOnly.toUpperCase()) {
return lettersOnly.toLowerCase();
}

const segments = splitCamelCase(lettersOnly);
const prefix = buildPrefixFromSegments(segments, true);
return prefix.length > 0 ? prefix : safeFallback;
}

const segments = words.flatMap((word) => {
const lettersOnly = word.replace(/[^a-zA-Z]/g, '');
if (!lettersOnly) return [];
return splitCamelCase(lettersOnly);
});

const prefix = buildPrefixFromSegments(segments, false);
return prefix.length > 0 ? prefix : safeFallback;
}

export { generatePrefix };
5 changes: 5 additions & 0 deletions src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ export * from './HttpHandler';
export * from './CookieHelper';
export * from './UrlTemplate';
export * from './PatternCompiler';
export * from './generatePrefix';
export * from './LabelMapper';
export * from './FormSanitizer';
export * from './InfoFormatter';
export * from './LocalizationHandler';
13 changes: 13 additions & 0 deletions tsconfig.jest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "node"],
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["__tests__/**/*", "src/**/*"],
"exclude": ["dist", "node_modules"]
}
Loading
Loading