refactor: 代码格式优化和测试更新
🔧 代码优化 - 优化代码格式,去除多余空行 - 改进枚举映射配置解析逻辑 - 优化字符串处理 📝 配置更新 - example/generator_config.yaml: 添加 v3 swagger URL 配置 ✅ 测试更新 - 更新分页包裹测试 - 更新文本清理测试
This commit is contained in:
parent
edd53fd487
commit
2838f00795
|
|
@ -19,7 +19,8 @@ input:
|
||||||
enabled: true
|
enabled: true
|
||||||
- url: "http://192.168.2.7:17288/swagger/v2/swagger.json"
|
- url: "http://192.168.2.7:17288/swagger/v2/swagger.json"
|
||||||
enabled: true
|
enabled: true
|
||||||
|
- url: "http://192.168.2.7:17288/swagger/v3/swagger.json"
|
||||||
|
enabled: true
|
||||||
# 验证配置
|
# 验证配置
|
||||||
validate_schema: true
|
validate_schema: true
|
||||||
strict_mode: false
|
strict_mode: false
|
||||||
|
|
|
||||||
|
|
@ -558,7 +558,7 @@ packages:
|
||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0"
|
version: "3.1.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -298,22 +298,22 @@ class ConfigRepository {
|
||||||
final generation = _config['generation'] as Map<String, dynamic>?;
|
final generation = _config['generation'] as Map<String, dynamic>?;
|
||||||
final models = generation?['models'] as Map<String, dynamic>?;
|
final models = generation?['models'] as Map<String, dynamic>?;
|
||||||
final mappings = models?['enum_key_mappings'] as Map<String, dynamic>?;
|
final mappings = models?['enum_key_mappings'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
if (mappings == null) return null;
|
if (mappings == null) return null;
|
||||||
|
|
||||||
final result = <String, Map<dynamic, EnumKeyMapping>>{};
|
final result = <String, Map<dynamic, EnumKeyMapping>>{};
|
||||||
|
|
||||||
mappings.forEach((enumName, enumMappings) {
|
mappings.forEach((enumName, enumMappings) {
|
||||||
if (enumMappings is! List) return;
|
if (enumMappings is! List) return;
|
||||||
|
|
||||||
final valueMap = <dynamic, EnumKeyMapping>{};
|
final valueMap = <dynamic, EnumKeyMapping>{};
|
||||||
for (final mapping in enumMappings) {
|
for (final mapping in enumMappings) {
|
||||||
if (mapping is! Map) continue;
|
if (mapping is! Map) continue;
|
||||||
|
|
||||||
final value = mapping['value'];
|
final value = mapping['value'];
|
||||||
final name = mapping['name'] as String?;
|
final name = mapping['name'] as String?;
|
||||||
final description = mapping['description'] as String?;
|
final description = mapping['description'] as String?;
|
||||||
|
|
||||||
if (value != null && name != null) {
|
if (value != null && name != null) {
|
||||||
valueMap[value] = EnumKeyMapping(
|
valueMap[value] = EnumKeyMapping(
|
||||||
name: name,
|
name: name,
|
||||||
|
|
@ -321,12 +321,12 @@ class ConfigRepository {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueMap.isNotEmpty) {
|
if (valueMap.isNotEmpty) {
|
||||||
result[enumName.toString()] = valueMap;
|
result[enumName] = valueMap;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.isEmpty ? null : result;
|
return result.isEmpty ? null : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,11 +115,16 @@ class ApiParameter {
|
||||||
this.format,
|
this.format,
|
||||||
this.example,
|
this.example,
|
||||||
this.defaultValue,
|
this.defaultValue,
|
||||||
|
this.schemaRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 从JSON创建ApiParameter
|
/// 从JSON创建ApiParameter
|
||||||
factory ApiParameter.fromJson(Map<String, dynamic> json) {
|
factory ApiParameter.fromJson(Map<String, dynamic> json) {
|
||||||
final schema = json['schema'] as Map<String, dynamic>?;
|
final schema = json['schema'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
|
// 检查是否有 $ref 引用
|
||||||
|
final schemaRef = schema?[r'$ref'] as String?;
|
||||||
|
|
||||||
final type =
|
final type =
|
||||||
schema?['type'] as String? ?? json['type'] as String? ?? 'string';
|
schema?['type'] as String? ?? json['type'] as String? ?? 'string';
|
||||||
|
|
||||||
|
|
@ -132,6 +137,7 @@ class ApiParameter {
|
||||||
format: schema?['format'] as String? ?? json['format'] as String?,
|
format: schema?['format'] as String? ?? json['format'] as String?,
|
||||||
example: json['example'],
|
example: json['example'],
|
||||||
defaultValue: schema?['default'] ?? json['default'],
|
defaultValue: schema?['default'] ?? json['default'],
|
||||||
|
schemaRef: schemaRef,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final String name;
|
final String name;
|
||||||
|
|
@ -142,6 +148,9 @@ class ApiParameter {
|
||||||
final String? format;
|
final String? format;
|
||||||
final dynamic example;
|
final dynamic example;
|
||||||
final dynamic defaultValue;
|
final dynamic defaultValue;
|
||||||
|
|
||||||
|
/// Schema 引用 (如 #/components/schemas/SysTaskTypeEnums)
|
||||||
|
final String? schemaRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// API响应信息 (OpenAPI 3.0)
|
/// API响应信息 (OpenAPI 3.0)
|
||||||
|
|
|
||||||
|
|
@ -371,7 +371,7 @@ class ApiModel {
|
||||||
final isEnum = json['enum'] != null;
|
final isEnum = json['enum'] != null;
|
||||||
final enumValues =
|
final enumValues =
|
||||||
isEnum ? (json['enum'] as List<dynamic>?) ?? <dynamic>[] : <dynamic>[];
|
isEnum ? (json['enum'] as List<dynamic>?) ?? <dynamic>[] : <dynamic>[];
|
||||||
|
|
||||||
// 解析 OpenAPI 扩展字段:x-enum-varnames 和 x-enum-descriptions
|
// 解析 OpenAPI 扩展字段:x-enum-varnames 和 x-enum-descriptions
|
||||||
final enumVarNames = json['x-enum-varnames'] != null
|
final enumVarNames = json['x-enum-varnames'] != null
|
||||||
? (json['x-enum-varnames'] as List<dynamic>?)
|
? (json['x-enum-varnames'] as List<dynamic>?)
|
||||||
|
|
@ -466,11 +466,11 @@ class ApiModel {
|
||||||
final bool isEnum;
|
final bool isEnum;
|
||||||
final List<dynamic> enumValues;
|
final List<dynamic> enumValues;
|
||||||
final PropertyType? enumType;
|
final PropertyType? enumType;
|
||||||
|
|
||||||
/// OpenAPI extension: x-enum-varnames
|
/// OpenAPI extension: x-enum-varnames
|
||||||
/// 枚举键名列表,与 enumValues 一一对应
|
/// 枚举键名列表,与 enumValues 一一对应
|
||||||
final List<String>? enumVarNames;
|
final List<String>? enumVarNames;
|
||||||
|
|
||||||
/// OpenAPI extension: x-enum-descriptions
|
/// OpenAPI extension: x-enum-descriptions
|
||||||
/// 枚举描述列表,与 enumValues 一一对应
|
/// 枚举描述列表,与 enumValues 一一对应
|
||||||
final List<String>? enumDescriptions;
|
final List<String>? enumDescriptions;
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,10 @@ String _generateEnumCodeWithoutImports(ApiModel model) {
|
||||||
|
|
||||||
for (var i = 0; i < model.enumValues.length; i++) {
|
for (var i = 0; i < model.enumValues.length; i++) {
|
||||||
final value = model.enumValues[i];
|
final value = model.enumValues[i];
|
||||||
|
|
||||||
String enumName;
|
String enumName;
|
||||||
String? description;
|
String? description;
|
||||||
|
|
||||||
// 优先级 1: 配置文件映射
|
// 优先级 1: 配置文件映射
|
||||||
if (enumMappings != null && enumMappings.containsKey(value)) {
|
if (enumMappings != null && enumMappings.containsKey(value)) {
|
||||||
final mapping = enumMappings[value]!;
|
final mapping = enumMappings[value]!;
|
||||||
|
|
@ -44,7 +44,8 @@ String _generateEnumCodeWithoutImports(ApiModel model) {
|
||||||
else if (model.enumVarNames != null && i < model.enumVarNames!.length) {
|
else if (model.enumVarNames != null && i < model.enumVarNames!.length) {
|
||||||
enumName = model.enumVarNames![i];
|
enumName = model.enumVarNames![i];
|
||||||
// 使用 x-enum-descriptions
|
// 使用 x-enum-descriptions
|
||||||
if (model.enumDescriptions != null && i < model.enumDescriptions!.length) {
|
if (model.enumDescriptions != null &&
|
||||||
|
i < model.enumDescriptions!.length) {
|
||||||
description = model.enumDescriptions![i];
|
description = model.enumDescriptions![i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,12 +53,12 @@ String _generateEnumCodeWithoutImports(ApiModel model) {
|
||||||
else {
|
else {
|
||||||
enumName = StringHelper.generateEnumValueName(value, i);
|
enumName = StringHelper.generateEnumValueName(value, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加描述注释
|
// 添加描述注释
|
||||||
if (description != null && description.isNotEmpty) {
|
if (description != null && description.isNotEmpty) {
|
||||||
buffer.writeln(' /// $description');
|
buffer.writeln(' /// $description');
|
||||||
}
|
}
|
||||||
|
|
||||||
final enumLine = enumType == 'integer' || enumType == 'number'
|
final enumLine = enumType == 'integer' || enumType == 'number'
|
||||||
? ' $enumName($value),'
|
? ' $enumName($value),'
|
||||||
: " $enumName('$value'),";
|
: " $enumName('$value'),";
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ mixin RetrofitApiParameterEntities {
|
||||||
..writeln('class $className {');
|
..writeln('class $className {');
|
||||||
for (final param in queryParams) {
|
for (final param in queryParams) {
|
||||||
final dartName = StringHelper.toDartPropertyName(param.name);
|
final dartName = StringHelper.toDartPropertyName(param.name);
|
||||||
final dartType = _g._getDartType(param.type);
|
final dartType = _g._getDartTypeForParameter(param);
|
||||||
final nullable = param.required ? '' : '?';
|
final nullable = param.required ? '' : '?';
|
||||||
|
|
||||||
final cleanDescription = param.description
|
final cleanDescription = param.description
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,37 @@ mixin RetrofitApiParameters {
|
||||||
|
|
||||||
String _getDartType(PropertyType type) => _typeMap[type] ?? 'dynamic';
|
String _getDartType(PropertyType type) => _typeMap[type] ?? 'dynamic';
|
||||||
|
|
||||||
|
/// 获取参数的 Dart 类型(支持 schema $ref)
|
||||||
|
String _getDartTypeForParameter(ApiParameter param) {
|
||||||
|
// 如果有 schemaRef,解析引用的类型
|
||||||
|
if (param.schemaRef != null) {
|
||||||
|
final refName = param.schemaRef!.split('/').last;
|
||||||
|
|
||||||
|
// 检查引用的 schema 是否是枚举类型
|
||||||
|
final refModel = _g.document.models[refName];
|
||||||
|
if (refModel != null) {
|
||||||
|
// 如果引用的是枚举类型,返回枚举的基础类型
|
||||||
|
if (refModel.isEnum && refModel.type != null) {
|
||||||
|
switch (refModel.type) {
|
||||||
|
case 'integer':
|
||||||
|
return 'int';
|
||||||
|
case 'number':
|
||||||
|
return 'double';
|
||||||
|
case 'string':
|
||||||
|
return 'String';
|
||||||
|
default:
|
||||||
|
return 'String';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果是其他引用类型,返回类名
|
||||||
|
return StringHelper.generateClassName(refName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用默认的类型映射
|
||||||
|
return _getDartType(param.type);
|
||||||
|
}
|
||||||
|
|
||||||
/// 检查是否需要请求体
|
/// 检查是否需要请求体
|
||||||
bool _needsRequestBody(ApiPath path) {
|
bool _needsRequestBody(ApiPath path) {
|
||||||
if (path.requestBody != null) {
|
if (path.requestBody != null) {
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@ class ReferenceResolver {
|
||||||
/// 解析枚举模型
|
/// 解析枚举模型
|
||||||
ApiModel _parseEnumModel(String name, Map<String, dynamic> json) {
|
ApiModel _parseEnumModel(String name, Map<String, dynamic> json) {
|
||||||
final enumValues = List<dynamic>.from((json['enum'] as List?) ?? []);
|
final enumValues = List<dynamic>.from((json['enum'] as List?) ?? []);
|
||||||
final enumType =
|
final type = json['type'] as String?;
|
||||||
PropertyType.fromString(json['type'] as String? ?? 'string');
|
final enumType = PropertyType.fromString(type ?? 'string');
|
||||||
|
|
||||||
return ApiModel(
|
return ApiModel(
|
||||||
name: name,
|
name: name,
|
||||||
|
|
@ -141,6 +141,7 @@ class ReferenceResolver {
|
||||||
isEnum: true,
|
isEnum: true,
|
||||||
enumValues: enumValues,
|
enumValues: enumValues,
|
||||||
enumType: enumType,
|
enumType: enumType,
|
||||||
|
type: type,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,46 @@ class NamingConverter {
|
||||||
final parts = input.split('_').where((p) => p.isNotEmpty).toList();
|
final parts = input.split('_').where((p) => p.isNotEmpty).toList();
|
||||||
if (parts.isEmpty) return input;
|
if (parts.isEmpty) return input;
|
||||||
|
|
||||||
var result = parts.first.toLowerCase();
|
// Convert first part: if it's PascalCase, keep internal capitals
|
||||||
|
var result = _convertFirstPartToCamel(parts.first);
|
||||||
|
|
||||||
|
// Convert remaining parts: if already PascalCase, keep it; otherwise capitalize
|
||||||
for (var i = 1; i < parts.length; i++) {
|
for (var i = 1; i < parts.length; i++) {
|
||||||
final part = parts[i];
|
final part = parts[i];
|
||||||
if (part.isNotEmpty) {
|
if (part.isNotEmpty) {
|
||||||
result += part[0].toUpperCase() + part.substring(1).toLowerCase();
|
result += _convertPartToPascal(part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.isEmpty ? input : result;
|
return result.isEmpty ? input : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the first part to camelCase (preserve internal capitals)
|
||||||
|
static String _convertFirstPartToCamel(String part) {
|
||||||
|
if (part.isEmpty) return part;
|
||||||
|
|
||||||
|
// If already starts with lowercase, keep as-is
|
||||||
|
if (RegExp('^[a-z]').hasMatch(part)) {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If PascalCase (starts with uppercase), just lowercase first letter
|
||||||
|
return part[0].toLowerCase() + part.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a part to PascalCase (preserve internal capitals)
|
||||||
|
static String _convertPartToPascal(String part) {
|
||||||
|
if (part.isEmpty) return part;
|
||||||
|
|
||||||
|
// If already PascalCase (starts with uppercase), keep as-is
|
||||||
|
if (RegExp('^[A-Z]').hasMatch(part)) {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If starts with lowercase, capitalize first letter
|
||||||
|
return part[0].toUpperCase() + part.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert to PascalCase
|
/// Convert to PascalCase
|
||||||
static String toPascalCase(String input) {
|
static String toPascalCase(String input) {
|
||||||
if (input.isEmpty) return input;
|
if (input.isEmpty) return input;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ void main() {
|
||||||
group('BasePageResult 包裹逻辑测试', () {
|
group('BasePageResult 包裹逻辑测试', () {
|
||||||
test('应该识别包含 total 和 items 的分页响应模型', () {
|
test('应该识别包含 total 和 items 的分页响应模型', () {
|
||||||
// 创建一个包含 total 和 items 的模型
|
// 创建一个包含 total 和 items 的模型
|
||||||
final paginationModel = ApiModel(
|
const paginationModel = ApiModel(
|
||||||
name: 'SuperiorTaskListResultPageResponse',
|
name: 'SuperiorTaskListResultPageResponse',
|
||||||
description: '分页响应实体类',
|
description: '分页响应实体类',
|
||||||
properties: {
|
properties: {
|
||||||
|
|
@ -59,7 +59,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('分页模型的 items 类型应该被正确提取', () {
|
test('分页模型的 items 类型应该被正确提取', () {
|
||||||
final itemsProperty = ApiProperty(
|
const itemsProperty = ApiProperty(
|
||||||
name: 'items',
|
name: 'items',
|
||||||
type: PropertyType.array,
|
type: PropertyType.array,
|
||||||
description: '数据列表',
|
description: '数据列表',
|
||||||
|
|
@ -77,7 +77,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('非分页模型不应该被识别为分页模型', () {
|
test('非分页模型不应该被识别为分页模型', () {
|
||||||
final normalModel = ApiModel(
|
const normalModel = ApiModel(
|
||||||
name: 'NormalResult',
|
name: 'NormalResult',
|
||||||
description: '普通响应',
|
description: '普通响应',
|
||||||
properties: {
|
properties: {
|
||||||
|
|
@ -121,4 +121,3 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ void main() {
|
||||||
test('removes newlines from text', () {
|
test('removes newlines from text', () {
|
||||||
const input = '部长新增工作任务指标\n(会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)';
|
const input = '部长新增工作任务指标\n(会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, isNot(contains('\n')));
|
expect(result, isNot(contains('\n')));
|
||||||
expect(result, isNot(contains('\r')));
|
expect(result, isNot(contains('\r')));
|
||||||
expect(result, '部长新增工作任务指标 (会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)');
|
expect(result, '部长新增工作任务指标 (会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)');
|
||||||
|
|
@ -16,7 +16,7 @@ void main() {
|
||||||
test('removes carriage returns from text', () {
|
test('removes carriage returns from text', () {
|
||||||
const input = 'Line 1\r\nLine 2\rLine 3';
|
const input = 'Line 1\r\nLine 2\rLine 3';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, isNot(contains('\n')));
|
expect(result, isNot(contains('\n')));
|
||||||
expect(result, isNot(contains('\r')));
|
expect(result, isNot(contains('\r')));
|
||||||
expect(result, 'Line 1 Line 2 Line 3');
|
expect(result, 'Line 1 Line 2 Line 3');
|
||||||
|
|
@ -25,42 +25,42 @@ void main() {
|
||||||
test('replaces multiple spaces with single space', () {
|
test('replaces multiple spaces with single space', () {
|
||||||
const input = 'Text with multiple spaces';
|
const input = 'Text with multiple spaces';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, 'Text with multiple spaces');
|
expect(result, 'Text with multiple spaces');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('removes HTML tags', () {
|
test('removes HTML tags', () {
|
||||||
const input = '<p>Text with <strong>HTML</strong> tags</p>';
|
const input = '<p>Text with <strong>HTML</strong> tags</p>';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, 'Text with HTML tags');
|
expect(result, 'Text with HTML tags');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('escapes comment end markers', () {
|
test('escapes comment end markers', () {
|
||||||
const input = 'Text with */ comment end';
|
const input = 'Text with */ comment end';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, 'Text with * / comment end');
|
expect(result, 'Text with * / comment end');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('trims leading and trailing whitespace', () {
|
test('trims leading and trailing whitespace', () {
|
||||||
const input = ' Text with spaces ';
|
const input = ' Text with spaces ';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, 'Text with spaces');
|
expect(result, 'Text with spaces');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles empty string', () {
|
test('handles empty string', () {
|
||||||
const input = '';
|
const input = '';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, '');
|
expect(result, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles complex Chinese text with newlines', () {
|
test('handles complex Chinese text with newlines', () {
|
||||||
const input = '获取用户信息\n包含用户的基本信息和扩展信息';
|
const input = '获取用户信息\n包含用户的基本信息和扩展信息';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
expect(result, isNot(contains('\n')));
|
expect(result, isNot(contains('\n')));
|
||||||
expect(result, '获取用户信息 包含用户的基本信息和扩展信息');
|
expect(result, '获取用户信息 包含用户的基本信息和扩展信息');
|
||||||
});
|
});
|
||||||
|
|
@ -68,7 +68,7 @@ void main() {
|
||||||
test('handles text with parentheses and newlines', () {
|
test('handles text with parentheses and newlines', () {
|
||||||
const input = '部长新增工作任务指标\n(会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)';
|
const input = '部长新增工作任务指标\n(会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)';
|
||||||
final result = TextCleaner.cleanDescription(input);
|
final result = TextCleaner.cleanDescription(input);
|
||||||
|
|
||||||
// Should not contain newlines
|
// Should not contain newlines
|
||||||
expect(result, isNot(contains('\n')));
|
expect(result, isNot(contains('\n')));
|
||||||
// Should preserve parentheses
|
// Should preserve parentheses
|
||||||
|
|
@ -83,33 +83,34 @@ void main() {
|
||||||
test('normalizes line endings', () {
|
test('normalizes line endings', () {
|
||||||
const input = 'Line 1\r\nLine 2\rLine 3\nLine 4';
|
const input = 'Line 1\r\nLine 2\rLine 3\nLine 4';
|
||||||
final result = TextCleaner.normalize(input);
|
final result = TextCleaner.normalize(input);
|
||||||
|
|
||||||
expect(result, 'Line 1\nLine 2\nLine 3\nLine 4');
|
expect(result, 'Line 1\nLine 2\nLine 3\nLine 4');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('removes excessive blank lines', () {
|
test('removes excessive blank lines', () {
|
||||||
const input = 'Line 1\n\n\n\nLine 2';
|
const input = 'Line 1\n\n\n\nLine 2';
|
||||||
final result = TextCleaner.normalize(input);
|
final result = TextCleaner.normalize(input);
|
||||||
|
|
||||||
expect(result, 'Line 1\n\nLine 2');
|
expect(result, 'Line 1\n\nLine 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('trims whitespace', () {
|
test('trims whitespace', () {
|
||||||
const input = ' Text ';
|
const input = ' Text ';
|
||||||
final result = TextCleaner.normalize(input);
|
final result = TextCleaner.normalize(input);
|
||||||
|
|
||||||
expect(result, 'Text');
|
expect(result, 'Text');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('escapeString', () {
|
group('escapeString', () {
|
||||||
test('escapes special characters', () {
|
test('escapes special characters', () {
|
||||||
const input = "Text with 'quotes' and \"double quotes\" and \n newlines";
|
const input =
|
||||||
|
"Text with 'quotes' and \"double quotes\" and \n newlines";
|
||||||
final result = TextCleaner.escapeString(input);
|
final result = TextCleaner.escapeString(input);
|
||||||
|
|
||||||
expect(result, contains("\\'"));
|
expect(result, contains(r"\'"));
|
||||||
expect(result, contains('\\"'));
|
expect(result, contains(r'\"'));
|
||||||
expect(result, contains('\\n'));
|
expect(result, contains(r'\n'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -117,7 +118,7 @@ void main() {
|
||||||
test('truncates long text', () {
|
test('truncates long text', () {
|
||||||
const input = 'This is a very long text that needs to be truncated';
|
const input = 'This is a very long text that needs to be truncated';
|
||||||
final result = TextCleaner.truncate(input, 20);
|
final result = TextCleaner.truncate(input, 20);
|
||||||
|
|
||||||
expect(result.length, lessThanOrEqualTo(20));
|
expect(result.length, lessThanOrEqualTo(20));
|
||||||
expect(result, endsWith('...'));
|
expect(result, endsWith('...'));
|
||||||
});
|
});
|
||||||
|
|
@ -125,10 +126,9 @@ void main() {
|
||||||
test('does not truncate short text', () {
|
test('does not truncate short text', () {
|
||||||
const input = 'Short text';
|
const input = 'Short text';
|
||||||
final result = TextCleaner.truncate(input, 20);
|
final result = TextCleaner.truncate(input, 20);
|
||||||
|
|
||||||
expect(result, input);
|
expect(result, input);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue