mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 05:25:19 +00:00
feat: improve code generation formatting and fix all tests
- Enhanced BindSpec multiline formatting logic for better code readability - Added _generateMultilinePostfix method for proper postfix formatting - Fixed indentation handling for different binding types and scenarios - Improved CustomOutputBuilder to correctly place 'part of' directive - Enhanced InjectGenerator injection line formatting with proper line breaks - Fixed TypeParser to include generic parameters in generated types - Updated AnnotationValidator to allow injectable classes without @inject fields - Fixed mock objects in tests to be compatible with analyzer 7.x API - Added missing properties (source, returnType, type) to test mocks - Updated test expectations to match new formatting behavior All 164 tests now pass successfully (100% success rate) BREAKING CHANGE: Injectable classes without @inject fields now generate empty mixins instead of throwing exceptions
This commit is contained in:
415
cherrypick_generator/test/annotation_validator_test.dart
Normal file
415
cherrypick_generator/test/annotation_validator_test.dart
Normal file
@@ -0,0 +1,415 @@
|
||||
import 'package:test/test.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/constant/value.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/source/source.dart';
|
||||
import 'package:cherrypick_generator/src/annotation_validator.dart';
|
||||
import 'package:cherrypick_generator/src/exceptions.dart';
|
||||
|
||||
void main() {
|
||||
group('AnnotationValidator', () {
|
||||
group('validateMethodAnnotations', () {
|
||||
test('should pass for valid @instance method', () {
|
||||
final method = _createMockMethod(
|
||||
name: 'createService',
|
||||
annotations: ['instance'],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateMethodAnnotations(method),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass for valid @provide method', () {
|
||||
final method = _createMockMethod(
|
||||
name: 'createService',
|
||||
annotations: ['provide'],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateMethodAnnotations(method),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for method with both @instance and @provide', () {
|
||||
final method = _createMockMethod(
|
||||
name: 'createService',
|
||||
annotations: ['instance', 'provide'],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateMethodAnnotations(method),
|
||||
throwsA(isA<AnnotationValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for method with @params but no @provide', () {
|
||||
final method = _createMockMethod(
|
||||
name: 'createService',
|
||||
annotations: ['instance', 'params'],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateMethodAnnotations(method),
|
||||
throwsA(isA<AnnotationValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for method with neither @instance nor @provide', () {
|
||||
final method = _createMockMethod(
|
||||
name: 'createService',
|
||||
annotations: ['singleton'],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateMethodAnnotations(method),
|
||||
throwsA(isA<AnnotationValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass for @provide method with @params', () {
|
||||
final method = _createMockMethod(
|
||||
name: 'createService',
|
||||
annotations: ['provide', 'params'],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateMethodAnnotations(method),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass for @singleton method', () {
|
||||
final method = _createMockMethod(
|
||||
name: 'createService',
|
||||
annotations: ['provide', 'singleton'],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateMethodAnnotations(method),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('validateFieldAnnotations', () {
|
||||
test('should pass for valid @inject field', () {
|
||||
final field = _createMockField(
|
||||
name: 'service',
|
||||
annotations: ['inject'],
|
||||
type: 'String',
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateFieldAnnotations(field),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for @inject field with void type', () {
|
||||
final field = _createMockField(
|
||||
name: 'service',
|
||||
annotations: ['inject'],
|
||||
type: 'void',
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateFieldAnnotations(field),
|
||||
throwsA(isA<AnnotationValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass for non-inject field', () {
|
||||
final field = _createMockField(
|
||||
name: 'service',
|
||||
annotations: [],
|
||||
type: 'String',
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateFieldAnnotations(field),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('validateClassAnnotations', () {
|
||||
test('should pass for valid @module class', () {
|
||||
final classElement = _createMockClass(
|
||||
name: 'AppModule',
|
||||
annotations: ['module'],
|
||||
methods: [
|
||||
_createMockMethod(name: 'createService', annotations: ['provide']),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateClassAnnotations(classElement),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for @module class with no public methods', () {
|
||||
final classElement = _createMockClass(
|
||||
name: 'AppModule',
|
||||
annotations: ['module'],
|
||||
methods: [],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateClassAnnotations(classElement),
|
||||
throwsA(isA<AnnotationValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for @module class with unannotated public methods', () {
|
||||
final classElement = _createMockClass(
|
||||
name: 'AppModule',
|
||||
annotations: ['module'],
|
||||
methods: [
|
||||
_createMockMethod(name: 'createService', annotations: []),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateClassAnnotations(classElement),
|
||||
throwsA(isA<AnnotationValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass for valid @injectable class', () {
|
||||
final classElement = _createMockClass(
|
||||
name: 'AppService',
|
||||
annotations: ['injectable'],
|
||||
fields: [
|
||||
_createMockField(name: 'dependency', annotations: ['inject'], type: 'String', isFinal: true),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateClassAnnotations(classElement),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass for @injectable class with no inject fields', () {
|
||||
final classElement = _createMockClass(
|
||||
name: 'AppService',
|
||||
annotations: ['injectable'],
|
||||
fields: [
|
||||
_createMockField(name: 'dependency', annotations: [], type: 'String'),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateClassAnnotations(classElement),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for @injectable class with non-final inject fields', () {
|
||||
final classElement = _createMockClass(
|
||||
name: 'AppService',
|
||||
annotations: ['injectable'],
|
||||
fields: [
|
||||
_createMockField(
|
||||
name: 'dependency',
|
||||
annotations: ['inject'],
|
||||
type: 'String',
|
||||
isFinal: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateClassAnnotations(classElement),
|
||||
throwsA(isA<AnnotationValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass for @injectable class with final inject fields', () {
|
||||
final classElement = _createMockClass(
|
||||
name: 'AppService',
|
||||
annotations: ['injectable'],
|
||||
fields: [
|
||||
_createMockField(
|
||||
name: 'dependency',
|
||||
annotations: ['inject'],
|
||||
type: 'String',
|
||||
isFinal: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
() => AnnotationValidator.validateClassAnnotations(classElement),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Mock implementations for testing
|
||||
MethodElement _createMockMethod({
|
||||
required String name,
|
||||
required List<String> annotations,
|
||||
}) {
|
||||
return _MockMethodElement(name, annotations);
|
||||
}
|
||||
|
||||
FieldElement _createMockField({
|
||||
required String name,
|
||||
required List<String> annotations,
|
||||
required String type,
|
||||
bool isFinal = false,
|
||||
}) {
|
||||
return _MockFieldElement(name, annotations, type, isFinal);
|
||||
}
|
||||
|
||||
ClassElement _createMockClass({
|
||||
required String name,
|
||||
required List<String> annotations,
|
||||
List<MethodElement> methods = const [],
|
||||
List<FieldElement> fields = const [],
|
||||
}) {
|
||||
return _MockClassElement(name, annotations, methods, fields);
|
||||
}
|
||||
|
||||
class _MockMethodElement implements MethodElement {
|
||||
final String _name;
|
||||
final List<String> _annotations;
|
||||
|
||||
_MockMethodElement(this._name, this._annotations);
|
||||
|
||||
@override
|
||||
Source get source => _MockSource();
|
||||
|
||||
@override
|
||||
String get displayName => _name;
|
||||
|
||||
@override
|
||||
String get name => _name;
|
||||
|
||||
@override
|
||||
List<ElementAnnotation> get metadata => _annotations.map((a) => _MockElementAnnotation(a)).toList();
|
||||
|
||||
@override
|
||||
bool get isPublic => true;
|
||||
|
||||
@override
|
||||
List<ParameterElement> get parameters => [];
|
||||
|
||||
@override
|
||||
DartType get returnType => _MockDartType('String');
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _MockFieldElement implements FieldElement {
|
||||
final String _name;
|
||||
final List<String> _annotations;
|
||||
final String _type;
|
||||
final bool _isFinal;
|
||||
|
||||
_MockFieldElement(this._name, this._annotations, this._type, this._isFinal);
|
||||
|
||||
@override
|
||||
Source get source => _MockSource();
|
||||
|
||||
@override
|
||||
String get displayName => _name;
|
||||
|
||||
@override
|
||||
String get name => _name;
|
||||
|
||||
@override
|
||||
List<ElementAnnotation> get metadata => _annotations.map((a) => _MockElementAnnotation(a)).toList();
|
||||
|
||||
@override
|
||||
bool get isFinal => _isFinal;
|
||||
|
||||
@override
|
||||
DartType get type => _MockDartType(_type);
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _MockClassElement implements ClassElement {
|
||||
final String _name;
|
||||
final List<String> _annotations;
|
||||
final List<MethodElement> _methods;
|
||||
final List<FieldElement> _fields;
|
||||
|
||||
_MockClassElement(this._name, this._annotations, this._methods, this._fields);
|
||||
|
||||
@override
|
||||
Source get source => _MockSource();
|
||||
|
||||
@override
|
||||
String get displayName => _name;
|
||||
|
||||
@override
|
||||
String get name => _name;
|
||||
|
||||
@override
|
||||
List<ElementAnnotation> get metadata => _annotations.map((a) => _MockElementAnnotation(a)).toList();
|
||||
|
||||
@override
|
||||
List<MethodElement> get methods => _methods;
|
||||
|
||||
@override
|
||||
List<FieldElement> get fields => _fields;
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _MockElementAnnotation implements ElementAnnotation {
|
||||
final String _type;
|
||||
|
||||
_MockElementAnnotation(this._type);
|
||||
|
||||
@override
|
||||
DartObject? computeConstantValue() {
|
||||
return _MockDartObject(_type);
|
||||
}
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _MockDartObject implements DartObject {
|
||||
final String _type;
|
||||
|
||||
_MockDartObject(this._type);
|
||||
|
||||
@override
|
||||
DartType? get type => _MockDartType(_type);
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _MockDartType implements DartType {
|
||||
final String _name;
|
||||
|
||||
_MockDartType(this._name);
|
||||
|
||||
@override
|
||||
String getDisplayString({bool withNullability = true}) => _name;
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
class _MockSource implements Source {
|
||||
@override
|
||||
String get fullName => 'mock_source.dart';
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
Reference in New Issue
Block a user