From 59f327f13f9dda49fe1f5f57e8fbea38e6e3b691 Mon Sep 17 00:00:00 2001 From: Tobias Ottenweller Date: Sun, 20 Jul 2025 21:25:41 +0200 Subject: [PATCH 1/2] chore: replace unmaintained spell_out_numbers --- .../lib/src/naming/name_utils.dart | 97 ++++++++++++-- packages/tonik_generate/pubspec.yaml | 2 +- .../test/src/naming/name_utils_test.dart | 121 ++++++++++++++++++ pubspec.yaml | 2 +- 4 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 packages/tonik_generate/test/src/naming/name_utils_test.dart diff --git a/packages/tonik_generate/lib/src/naming/name_utils.dart b/packages/tonik_generate/lib/src/naming/name_utils.dart index 56e3e22..6857c0e 100644 --- a/packages/tonik_generate/lib/src/naming/name_utils.dart +++ b/packages/tonik_generate/lib/src/naming/name_utils.dart @@ -1,5 +1,5 @@ import 'package:change_case/change_case.dart'; -import 'package:spell_out_numbers/spell_out_numbers.dart'; +import 'package:number_to_words_english/number_to_words_english.dart'; /// Default prefix used for empty or invalid enum values. const defaultEnumPrefix = 'value'; @@ -107,15 +107,57 @@ String ensureNotKeyword(String name) { final processedPart = part.replaceAll(RegExp('[^a-zA-Z0-9]'), ''); if (processedPart.isEmpty) return (processed: '', number: null); + /// Helper function to normalize case: only convert to lowercase if all caps + String normalizeCase(String text, {required bool isFirst}) { + if (text.isEmpty) return text; + + final isAllCaps = + text == text.toUpperCase() && text != text.toLowerCase(); + + // Special handling for Dart keywords: keep them lowercase for first part + if (isFirst && allKeywords.contains(text.toLowerCase())) { + return text.toLowerCase(); + } + + if (isFirst) { + // For first part, convert to lowercase if all caps, otherwise camelCase + if (isAllCaps) { + return text.toLowerCase(); + } else { + // For mixed case, convert to proper camelCase (first letter lowercase) + return text.length == 1 + ? text.toLowerCase() + : text.substring(0, 1).toLowerCase() + text.substring(1); + } + } else { + // For subsequent parts, convert to PascalCase if all caps, otherwise + // ensure PascalCase + if (isAllCaps) { + return text.toPascalCase(); + } else { + // For mixed case, ensure it starts with uppercase + return text.length == 1 + ? text.toUpperCase() + : text.substring(0, 1).toUpperCase() + text.substring(1); + } + } + } + // Handle numbers differently for first part vs subsequent parts if (isFirstPart) { final numberMatch = RegExp(r'^(\d+)(.+)$').firstMatch(processedPart); if (numberMatch != null) { final number = numberMatch.group(1)!; final rest = numberMatch.group(2)!; - return (processed: rest.toCamelCase(), number: number); + return ( + processed: normalizeCase(rest, isFirst: true), + number: number, + ); } - return (processed: processedPart.toCamelCase(), number: null); + return ( + processed: normalizeCase(processedPart, isFirst: true), + number: null, + ); } else { final numberMatch = RegExp( r'^(\d+)(.+)$|^(.+?)(\d+)$', @@ -127,18 +169,33 @@ String ensureNotKeyword(String name) { final trailingNumber = numberMatch.group(4); if (leadingNumber != null && leadingRest != null) { - return (processed: leadingRest.toPascalCase(), number: leadingNumber); + return ( + processed: normalizeCase(leadingRest, isFirst: false), + number: leadingNumber, + ); } else if (trailingBase != null && trailingNumber != null) { - return (processed: trailingBase.toPascalCase(), number: trailingNumber); + return ( + processed: normalizeCase(trailingBase, isFirst: false), + number: trailingNumber, + ); } } - return (processed: processedPart.toPascalCase(), number: null); + return ( + processed: normalizeCase(processedPart, isFirst: false), + number: null, + ); } } /// Splits a string into parts based on common separators and case boundaries. -List splitIntoParts(String value) => - value.split(RegExp(r'[_\- ]|(?=[A-Z])')); +List splitIntoParts(String value) { + // Split on explicit separators and case boundaries + final parts = value.split( + RegExp(r'[_\- ]|(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])'), + ); + + return parts.where((part) => part.isNotEmpty).toList(); +} /// Processes parts into a normalized name. String processPartsIntoName(List parts) { @@ -204,16 +261,28 @@ String normalizeSingle(String name, {bool preserveNumbers = false}) { /// Normalizes an enum value name, handling special cases like integers. String normalizeEnumValueName(String value) { - // For integer values, spell out the number - if (RegExp(r'^\d+$').hasMatch(value)) { + // Only spell out numbers if the entire value is just a number (no prefix) + if (RegExp(r'^-?\d+$').hasMatch(value)) { final number = int.parse(value); - final words = EnglishNumberScheme().toWord(number); + final words = number < 0 + ? 'minus ${NumberToWordsEnglish.convert(number.abs())}' + : NumberToWordsEnglish.convert(number); final normalized = normalizeSingle(words); - return normalized.isEmpty ? defaultEnumPrefix : normalized; + return normalized.isEmpty + ? defaultEnumPrefix + : normalized.toCamelCase(); } - final normalized = normalizeSingle(value); - return normalized.isEmpty ? defaultEnumPrefix : normalized; + // For values with prefixes (like ERROR_404), preserve numbers as-is + final normalized = normalizeSingle(value, preserveNumbers: true); + if (normalized.isEmpty) return defaultEnumPrefix; + + // Don't apply toCamelCase if the normalized value starts with $ + if (normalized.startsWith(r'$')) { + return normalized; + } + + return normalized.toCamelCase(); } /// Ensures uniqueness in a list of normalized names diff --git a/packages/tonik_generate/pubspec.yaml b/packages/tonik_generate/pubspec.yaml index ef81ba1..fcbd649 100644 --- a/packages/tonik_generate/pubspec.yaml +++ b/packages/tonik_generate/pubspec.yaml @@ -14,8 +14,8 @@ dependencies: dart_style: ^3.0.1 logging: ^1.3.0 meta: ^1.16.0 + number_to_words_english: ^2.0.2 path: ^1.9.1 - spell_out_numbers: ^1.0.0 tonik_core: ^0.0.6 dev_dependencies: diff --git a/packages/tonik_generate/test/src/naming/name_utils_test.dart b/packages/tonik_generate/test/src/naming/name_utils_test.dart new file mode 100644 index 0000000..336b77c --- /dev/null +++ b/packages/tonik_generate/test/src/naming/name_utils_test.dart @@ -0,0 +1,121 @@ +import 'package:test/test.dart'; +import 'package:tonik_generate/src/naming/name_utils.dart'; + +void main() { + group('normalizeEnumValueName', () { + group('number conversion', () { + test('converts single digits to words', () { + expect(normalizeEnumValueName('0'), 'zero'); + expect(normalizeEnumValueName('1'), 'one'); + expect(normalizeEnumValueName('2'), 'two'); + expect(normalizeEnumValueName('3'), 'three'); + expect(normalizeEnumValueName('9'), 'nine'); + }); + + test('converts teen numbers to words', () { + expect(normalizeEnumValueName('10'), 'ten'); + expect(normalizeEnumValueName('11'), 'eleven'); + expect(normalizeEnumValueName('15'), 'fifteen'); + expect(normalizeEnumValueName('19'), 'nineteen'); + }); + + test('converts larger numbers to exact expected output', () { + expect(normalizeEnumValueName('42'), 'fortyTwo'); + expect(normalizeEnumValueName('100'), 'oneHundred'); + expect(normalizeEnumValueName('123'), 'oneHundredTwentyThree'); + expect(normalizeEnumValueName('1000'), 'oneThousand'); + }); + + test('handles negative numbers with exact output', () { + expect(normalizeEnumValueName('-1'), 'minusOne'); + expect(normalizeEnumValueName('-42'), 'minusFortyTwo'); + expect(normalizeEnumValueName('-100'), 'minusOneHundred'); + expect(normalizeEnumValueName('-999'), 'minusNineHundredNinetyNine'); + }); + + test('produces camelCase identifiers', () { + // Based on existing test expectations + expect(normalizeEnumValueName('1'), 'one'); + expect(normalizeEnumValueName('2'), 'two'); + expect(normalizeEnumValueName('3'), 'three'); + }); + }); + + group('string normalization', () { + test('normalizes simple strings', () { + expect(normalizeEnumValueName('active'), 'active'); + expect(normalizeEnumValueName('inactive'), 'inactive'); + expect(normalizeEnumValueName('pending'), 'pending'); + }); + + test('handles case conversion properly', () { + expect(normalizeEnumValueName('ACTIVE'), 'active'); // Clean lowercase + expect(normalizeEnumValueName('InActive'), 'inActive'); + expect(normalizeEnumValueName('PENDING'), 'pending'); + }); + + test('handles strings with separators', () { + expect(normalizeEnumValueName('in-progress'), 'inProgress'); + expect(normalizeEnumValueName('not_started'), 'notStarted'); + expect(normalizeEnumValueName('on hold'), 'onHold'); + }); + + test('handles mixed alphanumeric strings', () { + expect(normalizeEnumValueName('status1'), 'status1'); + expect( + normalizeEnumValueName('1status'), + 'status1', + ); // Number moved to end + expect(normalizeEnumValueName('v2_final'), 'v2Final'); + }); + + test('comprehensive real-world enum value cases', () { + // Common API status codes and enum patterns + expect(normalizeEnumValueName('SUCCESS_CODE'), 'successCode'); + expect(normalizeEnumValueName('ERROR_404'), 'error404'); + expect(normalizeEnumValueName('HTTP_STATUS'), 'httpStatus'); + expect(normalizeEnumValueName('NOT_FOUND'), 'notFound'); + expect(normalizeEnumValueName('API_VERSION_2'), 'apiVersion2'); + expect(normalizeEnumValueName('USER-ACCOUNT'), 'userAccount'); + expect(normalizeEnumValueName('data_model'), 'dataModel'); + expect(normalizeEnumValueName('ADMIN'), 'admin'); + expect(normalizeEnumValueName('guest'), 'guest'); + expect(normalizeEnumValueName('999'), 'nineHundredNinetyNine'); + expect(normalizeEnumValueName('2024'), 'twoThousandTwentyFour'); + }); + }); + + group('edge cases', () { + test('handles empty and invalid inputs', () { + expect(normalizeEnumValueName(''), 'value'); + expect(normalizeEnumValueName('_'), 'value'); + expect(normalizeEnumValueName('__'), 'value'); + }); + + test('handles special characters', () { + expect(normalizeEnumValueName('!@#'), 'value'); + expect(normalizeEnumValueName('status!'), 'status'); + expect(normalizeEnumValueName('test@#123'), 'test123'); + }); + + test('handles leading underscores', () { + expect(normalizeEnumValueName('_active'), 'active'); + expect(normalizeEnumValueName('__pending'), 'pending'); + }); + test('matches expected enum generation behavior', () { + // These are the expectations from the existing enum generator tests + expect(normalizeEnumValueName('1'), 'one'); + expect(normalizeEnumValueName('2'), 'two'); + expect(normalizeEnumValueName('3'), 'three'); + }); + + test('produces clean, readable identifiers', () { + // Common enum value patterns should be clean and readable + expect(normalizeEnumValueName('SUCCESS'), 'success'); + expect(normalizeEnumValueName('ERROR'), 'error'); + expect(normalizeEnumValueName('PENDING'), 'pending'); + expect(normalizeEnumValueName('IN_PROGRESS'), 'inProgress'); + }); + }); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index 9d59be4..ac6c8cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,8 +15,8 @@ dependencies: dart_style: ^3.0.1 logging: ^1.3.0 meta: ^1.16.0 + number_to_words_english: ^2.0.2 path: ^1.9.1 - spell_out_numbers: ^1.0.0 tonik: ^0.0.4 tonik_core: ^0.0.4 tonik_generate: ^0.0.4 From 89d228866630b711eaaa430d09a4dff170d30b60 Mon Sep 17 00:00:00 2001 From: Tobias Ottenweller Date: Sun, 20 Jul 2025 22:15:07 +0200 Subject: [PATCH 2/2] cleanup --- .../lib/src/naming/name_utils.dart | 280 ++++++++++-------- packages/tonik_generate/pubspec.yaml | 1 - .../test/src/naming/name_utils_test.dart | 33 +++ pubspec.yaml | 1 - 4 files changed, 184 insertions(+), 131 deletions(-) diff --git a/packages/tonik_generate/lib/src/naming/name_utils.dart b/packages/tonik_generate/lib/src/naming/name_utils.dart index 6857c0e..70871a0 100644 --- a/packages/tonik_generate/lib/src/naming/name_utils.dart +++ b/packages/tonik_generate/lib/src/naming/name_utils.dart @@ -1,5 +1,4 @@ import 'package:change_case/change_case.dart'; -import 'package:number_to_words_english/number_to_words_english.dart'; /// Default prefix used for empty or invalid enum values. const defaultEnumPrefix = 'value'; @@ -89,6 +88,72 @@ const generatedClassTokens = { const Set allKeywords = {...dartKeywords, ...generatedClassTokens}; +/// Converts a number to its English word representation. +/// Supports numbers up to trillions. +String _numberToWords(int number) { + if (number == 0) return 'zero'; + + const ones = [ + '', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', + 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', + 'sixteen', 'seventeen', 'eighteen', 'nineteen' + ]; + + const tens = [ + '', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', + 'eighty', 'ninety' + ]; + + final result = []; + var remaining = number; + + if (remaining >= 1000000000000) { + result + ..add(_numberToWords(remaining ~/ 1000000000000)) + ..add('trillion'); + remaining %= 1000000000000; + } + + if (remaining >= 1000000000) { + result + ..add(_numberToWords(remaining ~/ 1000000000)) + ..add('billion'); + remaining %= 1000000000; + } + + if (remaining >= 1000000) { + result + ..add(_numberToWords(remaining ~/ 1000000)) + ..add('million'); + remaining %= 1000000; + } + + if (remaining >= 1000) { + result + ..add(_numberToWords(remaining ~/ 1000)) + ..add('thousand'); + remaining %= 1000; + } + + if (remaining >= 100) { + result + ..add(ones[remaining ~/ 100]) + ..add('hundred'); + remaining %= 100; + } + + if (remaining >= 20) { + result.add(tens[remaining ~/ 10]); + if (remaining % 10 != 0) { + result.add(ones[remaining % 10]); + } + } else if (remaining > 0) { + result.add(ones[remaining]); + } + + return result.join(' ').trim(); +} + /// Ensures a name is not a Dart keyword by adding a $ prefix if necessary. String ensureNotKeyword(String name) { if (allKeywords.contains(name.toCamelCase()) || @@ -98,137 +163,103 @@ String ensureNotKeyword(String name) { return name; } -/// Processes a part of a name, handling numbers and casing. -/// If [isFirstPart] is true, numbers at the start will be moved to the end. -({String processed, String? number}) processPart( - String part, { - required bool isFirstPart, -}) { - final processedPart = part.replaceAll(RegExp('[^a-zA-Z0-9]'), ''); - if (processedPart.isEmpty) return (processed: '', number: null); - - /// Helper function to normalize case: only convert to lowercase if all caps - String normalizeCase(String text, {required bool isFirst}) { - if (text.isEmpty) return text; +/// Splits text into tokens and normalizes each one. +String _normalizeText(String text, {bool preserveNumbers = false}) { + if (text.isEmpty) return ''; + + // Clean invalid characters but preserve separators for splitting + final cleaned = text.replaceAll(RegExp(r'[^a-zA-Z0-9_\-\s]'), ''); + + // Split on separators and case boundaries + final tokens = cleaned + .split(RegExp(r'[_\-\s]+|(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])')) + .where((token) => token.isNotEmpty) + .toList(); + + if (tokens.isEmpty) return ''; + + final result = []; + final numbersToAppend = []; + + for (var i = 0; i < tokens.length; i++) { + final token = tokens[i]; + final isFirst = i == 0; - final isAllCaps = - text == text.toUpperCase() && text != text.toLowerCase(); + // Extract numbers from token + final numberMatch = + RegExp(r'^(\d+)(.*)$|^(.+?)(\d+)$').firstMatch(token); - // Special handling for Dart keywords: keep them lowercase for first part - if (isFirst && allKeywords.contains(text.toLowerCase())) { - return text.toLowerCase(); - } + String textPart; + String? numberPart; - if (isFirst) { - // For first part, convert to lowercase if all caps, otherwise camelCase - if (isAllCaps) { - return text.toLowerCase(); + if (numberMatch != null) { + if (numberMatch.group(1) != null) { + // Leading number: 123abc + numberPart = numberMatch.group(1); + textPart = numberMatch.group(2) ?? ''; } else { - // For mixed case, convert to proper camelCase (first letter lowercase) - return text.length == 1 - ? text.toLowerCase() - : text.substring(0, 1).toLowerCase() + text.substring(1); + // Trailing number: abc123 + textPart = numberMatch.group(3) ?? ''; + numberPart = numberMatch.group(4); } + } else if (RegExp(r'^\d+$').hasMatch(token)) { + // Pure number + numberPart = token; + textPart = ''; } else { - // For subsequent parts, convert to PascalCase if all caps, otherwise - // ensure PascalCase - if (isAllCaps) { - return text.toPascalCase(); - } else { - // For mixed case, ensure it starts with uppercase - return text.length == 1 - ? text.toUpperCase() - : text.substring(0, 1).toUpperCase() + text.substring(1); - } + // No numbers + textPart = token; + numberPart = null; } - } - - // Handle numbers differently for first part vs subsequent parts - if (isFirstPart) { - final numberMatch = RegExp(r'^(\d+)(.+)$').firstMatch(processedPart); - if (numberMatch != null) { - final number = numberMatch.group(1)!; - final rest = numberMatch.group(2)!; - return ( - processed: normalizeCase(rest, isFirst: true), - number: number, - ); + + // Process text part + if (textPart.isNotEmpty) { + final normalized = _normalizeCasing(textPart, isFirst: isFirst); + result.add(normalized); } - return ( - processed: normalizeCase(processedPart, isFirst: true), - number: null, - ); - } else { - final numberMatch = RegExp( - r'^(\d+)(.+)$|^(.+?)(\d+)$', - ).firstMatch(processedPart); - if (numberMatch != null) { - final leadingNumber = numberMatch.group(1); - final leadingRest = numberMatch.group(2); - final trailingBase = numberMatch.group(3); - final trailingNumber = numberMatch.group(4); - - if (leadingNumber != null && leadingRest != null) { - return ( - processed: normalizeCase(leadingRest, isFirst: false), - number: leadingNumber, - ); - } else if (trailingBase != null && trailingNumber != null) { - return ( - processed: normalizeCase(trailingBase, isFirst: false), - number: trailingNumber, - ); + + // Handle numbers + if (numberPart != null) { + if (isFirst && textPart.isNotEmpty && + numberMatch?.group(1) != null) { + // Move leading numbers from first token to end + // (e.g., "1status" -> "status1") + numbersToAppend.add(numberPart); + } else { + // Keep numbers in place for trailing numbers or non-first tokens + result.add(numberPart); } } - return ( - processed: normalizeCase(processedPart, isFirst: false), - number: null, - ); } -} - -/// Splits a string into parts based on common separators and case boundaries. -List splitIntoParts(String value) { - // Split on explicit separators and case boundaries - final parts = value.split( - RegExp(r'[_\- ]|(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])'), - ); - return parts.where((part) => part.isNotEmpty).toList(); + // Append any numbers that were moved from the first token + result.addAll(numbersToAppend); + + return result.join(); } -/// Processes parts into a normalized name. -String processPartsIntoName(List parts) { - if (parts.isEmpty) return ''; - - final processedParts = []; - - // Process first part - final firstResult = processPart(parts.first, isFirstPart: true); - if (firstResult.processed.isNotEmpty) { - processedParts.add(firstResult.processed); - if (firstResult.number != null) { - processedParts.add(firstResult.number!); - } +/// Normalizes the casing of a text token. +String _normalizeCasing(String text, {required bool isFirst}) { + if (text.isEmpty) return text; + + final isAllCaps = text == text.toUpperCase() && text != text.toLowerCase(); + + // Special handling for keywords - keep them lowercase for first part only + if (isFirst && allKeywords.contains(text.toLowerCase())) { + return text.toLowerCase(); } - - // Process remaining parts - for (var i = 1; i < parts.length; i++) { - final result = processPart(parts[i], isFirstPart: false); - if (result.processed.isNotEmpty) { - processedParts.add(result.processed); - if (result.number != null) { - processedParts.add(result.number!); - } - } + + if (isFirst) { + return isAllCaps ? text.toLowerCase() : text.toCamelCase(); + } else { + return isAllCaps ? text.toPascalCase() : text.toPascalCase(); } - - return processedParts.join(); } + + /// Normalizes a single name to follow Dart guidelines. String normalizeSingle(String name, {bool preserveNumbers = false}) { - // Handle empty or underscore-only strings if (name.isEmpty || RegExp(r'^_+$').hasMatch(name)) { return ''; } @@ -237,25 +268,16 @@ String normalizeSingle(String name, {bool preserveNumbers = false}) { var processedName = name.replaceAll(RegExp('^_+'), ''); if (processedName.isEmpty) return ''; - // If we need to preserve numbers and the name is just a number, return it + // If preserving numbers and it's just a number, return as-is if (preserveNumbers && RegExp(r'^\d+$').hasMatch(processedName)) { return processedName; } - final parts = splitIntoParts(processedName); - processedName = processPartsIntoName(parts); - - // If preserving numbers, ensure we don't lose them in the normalization - if (preserveNumbers) { - final originalNumber = RegExp(r'\d+$').firstMatch(name)?.group(0); - final processedNumber = RegExp(r'\d+$').firstMatch(processedName)?.group(0); - if (originalNumber != null && processedNumber != originalNumber) { - // Remove any trailing numbers and append the original number - final baseProcessed = processedName.replaceAll(RegExp(r'\d+$'), ''); - processedName = '$baseProcessed$originalNumber'; - } - } - + processedName = _normalizeText( + processedName, + preserveNumbers: preserveNumbers, + ); + return ensureNotKeyword(processedName); } @@ -265,8 +287,8 @@ String normalizeEnumValueName(String value) { if (RegExp(r'^-?\d+$').hasMatch(value)) { final number = int.parse(value); final words = number < 0 - ? 'minus ${NumberToWordsEnglish.convert(number.abs())}' - : NumberToWordsEnglish.convert(number); + ? 'minus ${_numberToWords(number.abs())}' + : _numberToWords(number); final normalized = normalizeSingle(words); return normalized.isEmpty ? defaultEnumPrefix diff --git a/packages/tonik_generate/pubspec.yaml b/packages/tonik_generate/pubspec.yaml index fcbd649..d7c644a 100644 --- a/packages/tonik_generate/pubspec.yaml +++ b/packages/tonik_generate/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: dart_style: ^3.0.1 logging: ^1.3.0 meta: ^1.16.0 - number_to_words_english: ^2.0.2 path: ^1.9.1 tonik_core: ^0.0.6 diff --git a/packages/tonik_generate/test/src/naming/name_utils_test.dart b/packages/tonik_generate/test/src/naming/name_utils_test.dart index 336b77c..1109322 100644 --- a/packages/tonik_generate/test/src/naming/name_utils_test.dart +++ b/packages/tonik_generate/test/src/naming/name_utils_test.dart @@ -33,6 +33,39 @@ void main() { expect(normalizeEnumValueName('-999'), 'minusNineHundredNinetyNine'); }); + test('converts millions to exact expected output', () { + expect(normalizeEnumValueName('1000000'), 'oneMillion'); + expect(normalizeEnumValueName('2000000'), 'twoMillion'); + expect(normalizeEnumValueName('5000000'), 'fiveMillion'); + expect(normalizeEnumValueName('1500000'), + 'oneMillionFiveHundredThousand'); + }); + + test('converts billions to exact expected output', () { + expect(normalizeEnumValueName('1000000000'), 'oneBillion'); + expect(normalizeEnumValueName('3000000000'), 'threeBillion'); + expect(normalizeEnumValueName('7000000000'), 'sevenBillion'); + expect(normalizeEnumValueName('1500000000'), + 'oneBillionFiveHundredMillion'); + }); + + test('converts trillions to exact expected output', () { + expect(normalizeEnumValueName('1000000000000'), 'oneTrillion'); + expect(normalizeEnumValueName('5000000000000'), 'fiveTrillion'); + expect(normalizeEnumValueName('9000000000000'), 'nineTrillion'); + expect(normalizeEnumValueName('1500000000000'), + 'oneTrillionFiveHundredBillion'); + }); + + test('handles complex large numbers', () { + expect(normalizeEnumValueName('1234567890'), + 'oneBillionTwoHundredThirtyFourMillion' + 'FiveHundredSixtySevenThousandEightHundredNinety'); + expect(normalizeEnumValueName('999999999999'), + 'nineHundredNinetyNineBillionNineHundredNinetyNineMillion' + 'NineHundredNinetyNineThousandNineHundredNinetyNine'); + }); + test('produces camelCase identifiers', () { // Based on existing test expectations expect(normalizeEnumValueName('1'), 'one'); diff --git a/pubspec.yaml b/pubspec.yaml index ac6c8cb..4625da4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,6 @@ dependencies: dart_style: ^3.0.1 logging: ^1.3.0 meta: ^1.16.0 - number_to_words_english: ^2.0.2 path: ^1.9.1 tonik: ^0.0.4 tonik_core: ^0.0.4