diff --git a/cherrypick_generator/.gitignore b/cherrypick_generator/.gitignore index 6897175..ff123a0 100644 --- a/cherrypick_generator/.gitignore +++ b/cherrypick_generator/.gitignore @@ -25,4 +25,6 @@ doc/api/ melos_cherrypick_generator.iml -**/*.mocks.dart \ No newline at end of file +**/*.mocks.dart + +coverage \ No newline at end of file diff --git a/cherrypick_generator/build.yaml b/cherrypick_generator/build.yaml index 9541079..0a1e0b6 100644 --- a/cherrypick_generator/build.yaml +++ b/cherrypick_generator/build.yaml @@ -2,7 +2,7 @@ builders: module_generator: import: "package:cherrypick_generator/module_generator.dart" builder_factories: ["moduleBuilder"] - build_extensions: {".dart": [".cherrypick.g.dart"]} + build_extensions: {".dart": [".module.cherrypick.g.dart"]} auto_apply: dependents required_inputs: ["lib/**"] runs_before: [] @@ -10,7 +10,7 @@ builders: inject_generator: import: "package:cherrypick_generator/inject_generator.dart" builder_factories: ["injectBuilder"] - build_extensions: {".dart": [".cherrypick.g.dart"]} + build_extensions: {".dart": [".inject.cherrypick.g.dart"]} auto_apply: dependents required_inputs: ["lib/**"] runs_before: [] diff --git a/cherrypick_generator/coverage_analysis.py b/cherrypick_generator/coverage_analysis.py new file mode 100644 index 0000000..9bff466 --- /dev/null +++ b/cherrypick_generator/coverage_analysis.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +""" +Анализ покрытия тестами для CherryPick Generator +""" + +import re +import os + +def analyze_lcov_file(lcov_path): + """Анализирует LCOV файл и возвращает статистику покрытия""" + + if not os.path.exists(lcov_path): + print(f"❌ LCOV файл не найден: {lcov_path}") + return + + with open(lcov_path, 'r') as f: + content = f.read() + + # Разбиваем на секции по файлам + file_sections = content.split('SF:')[1:] # Убираем первую пустую секцию + + total_lines = 0 + total_hit = 0 + files_coverage = {} + + for section in file_sections: + lines = section.strip().split('\n') + if not lines: + continue + + file_path = lines[0] + file_name = os.path.basename(file_path) + + # Подсчитываем строки + da_lines = [line for line in lines if line.startswith('DA:')] + + file_total = len(da_lines) + file_hit = 0 + + for da_line in da_lines: + # DA:line_number,hit_count + parts = da_line.split(',') + if len(parts) >= 2: + hit_count = int(parts[1]) + if hit_count > 0: + file_hit += 1 + + if file_total > 0: + coverage_percent = (file_hit / file_total) * 100 + files_coverage[file_name] = { + 'total': file_total, + 'hit': file_hit, + 'percent': coverage_percent + } + + total_lines += file_total + total_hit += file_hit + + # Общая статистика + overall_percent = (total_hit / total_lines) * 100 if total_lines > 0 else 0 + + print("📊 АНАЛИЗ ПОКРЫТИЯ ТЕСТАМИ CHERRYPICK GENERATOR") + print("=" * 60) + + print(f"\n🎯 ОБЩАЯ СТАТИСТИКА:") + print(f" Всего строк кода: {total_lines}") + print(f" Покрыто тестами: {total_hit}") + print(f" Общее покрытие: {overall_percent:.1f}%") + + print(f"\n📁 ПОКРЫТИЕ ПО ФАЙЛАМ:") + + # Сортируем по проценту покрытия + sorted_files = sorted(files_coverage.items(), key=lambda x: x[1]['percent'], reverse=True) + + for file_name, stats in sorted_files: + percent = stats['percent'] + hit = stats['hit'] + total = stats['total'] + + # Эмодзи в зависимости от покрытия + if percent >= 80: + emoji = "✅" + elif percent >= 50: + emoji = "🟡" + else: + emoji = "❌" + + print(f" {emoji} {file_name:<25} {hit:>3}/{total:<3} ({percent:>5.1f}%)") + + print(f"\n🏆 РЕЙТИНГ КОМПОНЕНТОВ:") + + # Группируем по типам компонентов + core_files = ['bind_spec.dart', 'bind_parameters_spec.dart', 'generated_class.dart'] + utils_files = ['metadata_utils.dart'] + generator_files = ['module_generator.dart', 'inject_generator.dart'] + + def calculate_group_coverage(file_list): + group_total = sum(files_coverage.get(f, {}).get('total', 0) for f in file_list) + group_hit = sum(files_coverage.get(f, {}).get('hit', 0) for f in file_list) + return (group_hit / group_total * 100) if group_total > 0 else 0 + + core_coverage = calculate_group_coverage(core_files) + utils_coverage = calculate_group_coverage(utils_files) + generators_coverage = calculate_group_coverage(generator_files) + + print(f" 🔧 Core Components: {core_coverage:>5.1f}%") + print(f" 🛠️ Utils: {utils_coverage:>5.1f}%") + print(f" ⚙️ Generators: {generators_coverage:>5.1f}%") + + print(f"\n📈 РЕКОМЕНДАЦИИ:") + + # Файлы с низким покрытием + low_coverage = [(f, s) for f, s in files_coverage.items() if s['percent'] < 50] + if low_coverage: + print(" 🎯 Приоритет для улучшения:") + for file_name, stats in sorted(low_coverage, key=lambda x: x[1]['percent']): + print(f" • {file_name} ({stats['percent']:.1f}%)") + + # Файлы без покрытия + zero_coverage = [(f, s) for f, s in files_coverage.items() if s['percent'] == 0] + if zero_coverage: + print(" ❗ Требуют срочного внимания:") + for file_name, stats in zero_coverage: + print(f" • {file_name} (0% покрытия)") + + print(f"\n✨ ДОСТИЖЕНИЯ:") + high_coverage = [(f, s) for f, s in files_coverage.items() if s['percent'] >= 80] + if high_coverage: + print(" 🏅 Отлично протестированы:") + for file_name, stats in sorted(high_coverage, key=lambda x: x[1]['percent'], reverse=True): + print(f" • {file_name} ({stats['percent']:.1f}%)") + + return files_coverage, overall_percent + +if __name__ == "__main__": + lcov_path = "coverage/lcov.info" + analyze_lcov_file(lcov_path) diff --git a/cherrypick_generator/pubspec.yaml b/cherrypick_generator/pubspec.yaml index 0bcbe6c..fb359ce 100644 --- a/cherrypick_generator/pubspec.yaml +++ b/cherrypick_generator/pubspec.yaml @@ -14,13 +14,14 @@ environment: dependencies: cherrypick_annotations: ^1.1.0-dev.1 analyzer: ^7.0.0 - dart_style: ^3.0.1 + dart_style: ^3.0.0 build: ^2.4.1 - build_runner: ^2.4.15 source_gen: ^2.0.0 collection: ^1.18.0 dev_dependencies: - lints: ^5.0.0 + lints: ^4.0.0 mockito: ^5.4.4 test: ^1.25.8 + build_test: ^2.1.7 + build_runner: ^2.4.13 diff --git a/cherrypick_generator/test/bind_spec_test.dart b/cherrypick_generator/test/bind_spec_test.dart new file mode 100644 index 0000000..a4ee53c --- /dev/null +++ b/cherrypick_generator/test/bind_spec_test.dart @@ -0,0 +1,304 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'package:cherrypick_generator/src/bind_spec.dart'; +import 'package:test/test.dart'; + +void main() { + group('BindSpec Tests', () { + group('BindSpec Creation', () { + test('should create BindSpec with all properties', () { + final bindSpec = BindSpec( + returnType: 'ApiClient', + methodName: 'createApiClient', + isSingleton: true, + named: 'mainApi', + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: true, + hasParams: false, + ); + + expect(bindSpec.returnType, equals('ApiClient')); + expect(bindSpec.methodName, equals('createApiClient')); + expect(bindSpec.isSingleton, isTrue); + expect(bindSpec.named, equals('mainApi')); + expect(bindSpec.parameters, isEmpty); + expect(bindSpec.bindingType, equals(BindingType.provide)); + expect(bindSpec.isAsyncInstance, isFalse); + expect(bindSpec.isAsyncProvide, isTrue); + expect(bindSpec.hasParams, isFalse); + }); + + test('should create BindSpec with minimal properties', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + expect(bindSpec.returnType, equals('String')); + expect(bindSpec.methodName, equals('getString')); + expect(bindSpec.isSingleton, isFalse); + expect(bindSpec.named, isNull); + expect(bindSpec.bindingType, equals(BindingType.instance)); + }); + }); + + group('Bind Generation - Instance', () { + test('should generate simple instance bind', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, equals(' bind().toInstance(getString());')); + }); + + test('should generate singleton instance bind', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: true, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, + equals(' bind().toInstance(getString()).singleton();')); + }); + + test('should generate named instance bind', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + named: 'testString', + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect( + result, + equals( + " bind().toInstance(getString()).withName('testString');")); + }); + + test('should generate named singleton instance bind', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: true, + named: 'testString', + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect( + result, + equals( + " bind().toInstance(getString()).withName('testString').singleton();")); + }); + + test('should generate async instance bind', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: true, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect( + result, equals(' bind().toInstanceAsync(getString());')); + }); + }); + + group('Bind Generation - Provide', () { + test('should generate simple provide bind', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect( + result, equals(' bind().toProvide(() => getString());')); + }); + + test('should generate async provide bind', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: true, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, + equals(' bind().toProvideAsync(() => getString());')); + }); + + test('should generate provide bind with params', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: true, + ); + + final result = bindSpec.generateBind(4); + expect( + result, + equals( + ' bind().toProvideWithParams((args) => getString());')); + }); + + test('should generate async provide bind with params', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: true, + hasParams: true, + ); + + final result = bindSpec.generateBind(4); + expect( + result, + equals( + ' bind().toProvideAsyncWithParams((args) => getString());')); + }); + }); + + group('Complex Scenarios', () { + test('should generate bind with all options', () { + final bindSpec = BindSpec( + returnType: 'ApiClient', + methodName: 'createApiClient', + isSingleton: true, + named: 'mainApi', + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: true, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect( + result, + equals( + " bind().toProvideAsync(() => createApiClient()).withName('mainApi').singleton();")); + }); + + test('should handle different indentation', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result2 = bindSpec.generateBind(2); + expect(result2, startsWith(' ')); + + final result8 = bindSpec.generateBind(8); + expect(result8, startsWith(' ')); + }); + + test('should handle complex type names', () { + final bindSpec = BindSpec( + returnType: 'Map>', + methodName: 'getComplexData', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains('bind>>()')); + expect(result, contains('toProvide')); + expect(result, contains('getComplexData')); + }); + }); + + group('BindingType Enum', () { + test('should have correct enum values', () { + expect(BindingType.instance, isNotNull); + expect(BindingType.provide, isNotNull); + expect(BindingType.values, hasLength(2)); + expect(BindingType.values, contains(BindingType.instance)); + expect(BindingType.values, contains(BindingType.provide)); + }); + + test('should have correct string representation', () { + expect(BindingType.instance.toString(), contains('instance')); + expect(BindingType.provide.toString(), contains('provide')); + }); + }); + }); +} diff --git a/cherrypick_generator/test/cherrypick_generator_test.dart b/cherrypick_generator/test/cherrypick_generator_test.dart index 404cb67..47eef68 100644 --- a/cherrypick_generator/test/cherrypick_generator_test.dart +++ b/cherrypick_generator/test/cherrypick_generator_test.dart @@ -1,13 +1,32 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + import 'package:test/test.dart'; -void main() { - group('A group of tests', () { - setUp(() { - // Additional setup goes here. - }); +// Import working test suites +import 'simple_test.dart' as simple_tests; +import 'bind_spec_test.dart' as bind_spec_tests; +import 'metadata_utils_test.dart' as metadata_utils_tests; +// Import integration test suites (now working!) +import 'module_generator_test.dart' as module_generator_tests; +import 'inject_generator_test.dart' as inject_generator_tests; - test('First Test', () { - expect(2, 2); - }); +void main() { + group('CherryPick Generator Tests', () { + group('Simple Tests', simple_tests.main); + group('BindSpec Tests', bind_spec_tests.main); + group('MetadataUtils Tests', metadata_utils_tests.main); + group('ModuleGenerator Tests', module_generator_tests.main); + group('InjectGenerator Tests', inject_generator_tests.main); }); } diff --git a/cherrypick_generator/test/inject_generator_test.dart b/cherrypick_generator/test/inject_generator_test.dart new file mode 100644 index 0000000..9195424 --- /dev/null +++ b/cherrypick_generator/test/inject_generator_test.dart @@ -0,0 +1,604 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:cherrypick_generator/inject_generator.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:test/test.dart'; + +void main() { + group('InjectGenerator Tests', () { + setUp(() { + // InjectGenerator setup if needed + }); + + group('Basic Injection', () { + test('should generate mixin for simple injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + late final MyService service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().resolve(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate mixin for nullable injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + late final MyService? service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().tryResolve(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Named Injection', () { + test('should generate mixin for named injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + @named('myService') + late final MyService service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().resolve( + named: 'myService', + ); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate mixin for named nullable injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + @named('myService') + late final MyService? service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().tryResolve( + named: 'myService', + ); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Scoped Injection', () { + test('should generate mixin for scoped injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + @scope('userScope') + late final MyService service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = + CherryPick.openScope(scopeName: 'userScope').resolve(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate mixin for scoped named injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + @scope('userScope') + @named('myService') + late final MyService service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openScope( + scopeName: 'userScope', + ).resolve(named: 'myService'); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Async Injection', () { + test('should generate mixin for Future injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + late final Future service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().resolveAsync(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate mixin for nullable Future injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + late final Future service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().tryResolveAsync(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate mixin for named Future injection', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + @named('myService') + late final Future service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().resolveAsync( + named: 'myService', + ); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Multiple Fields', () { + test('should generate mixin for multiple injected fields', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class ApiService {} +class DatabaseService {} +class CacheService {} + +@injectable() +class TestWidget { + @inject() + late final ApiService apiService; + + @inject() + @named('cache') + late final CacheService? cacheService; + + @inject() + @scope('dbScope') + late final Future dbService; + + // Non-injected field should be ignored + String nonInjectedField = "test"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.apiService = CherryPick.openRootScope().resolve(); + instance.cacheService = CherryPick.openRootScope().tryResolve( + named: 'cache', + ); + instance.dbService = + CherryPick.openScope( + scopeName: 'dbScope', + ).resolveAsync(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Complex Types', () { + test('should handle generic types', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +@injectable() +class TestWidget { + @inject() + late final List stringList; + + @inject() + late final Map stringIntMap; + + @inject() + late final Future> futureStringList; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.stringList = CherryPick.openRootScope().resolve>(); + instance.stringIntMap = + CherryPick.openRootScope().resolve>(); + instance.futureStringList = + CherryPick.openRootScope().resolveAsync>(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Error Cases', () { + test('should throw error for non-class element', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +@injectable() +void notAClass() {} +'''; + + await expectLater( + () => _testGeneration(input, ''), + throwsA(isA()), + ); + }); + + test('should generate empty mixin for class without @inject fields', + () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +@injectable() +class TestWidget { + String normalField = "test"; + int anotherField = 42; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) {} +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Edge Cases', () { + test('should handle empty scope name', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + @scope('') + late final MyService service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().resolve(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should handle empty named value', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_widget.inject.cherrypick.g.dart'; + +class MyService {} + +@injectable() +class TestWidget { + @inject() + @named('') + late final MyService service; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_widget.dart'; + +// ************************************************************************** +// InjectGenerator +// ************************************************************************** + +mixin _\$TestWidget { + void _inject(TestWidget instance) { + instance.service = CherryPick.openRootScope().resolve(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + }); +} + +/// Helper function to test code generation +Future _testGeneration(String input, String expectedOutput) async { + await testBuilder( + injectBuilder(BuilderOptions.empty), + { + 'a|lib/test_widget.dart': input, + }, + outputs: { + 'a|lib/test_widget.inject.cherrypick.g.dart': expectedOutput, + }, + reader: await PackageAssetReader.currentIsolate(), + ); +} diff --git a/cherrypick_generator/test/metadata_utils_test.dart b/cherrypick_generator/test/metadata_utils_test.dart new file mode 100644 index 0000000..5f79c8b --- /dev/null +++ b/cherrypick_generator/test/metadata_utils_test.dart @@ -0,0 +1,72 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'package:cherrypick_generator/src/metadata_utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('MetadataUtils Tests', () { + group('Basic Functionality', () { + test('should handle empty metadata lists', () { + expect(MetadataUtils.anyMeta([], 'singleton'), isFalse); + expect(MetadataUtils.getNamedValue([]), isNull); + }); + + test('should be available for testing', () { + // This test ensures the MetadataUtils class is accessible + // More comprehensive tests would require mock setup or integration tests + expect(MetadataUtils, isNotNull); + }); + + test('should handle null inputs gracefully', () { + expect(MetadataUtils.anyMeta([], ''), isFalse); + expect(MetadataUtils.getNamedValue([]), isNull); + }); + + test('should have static methods available', () { + // Verify that the static methods exist and can be called + // This is a basic smoke test + expect(() => MetadataUtils.anyMeta([], 'test'), returnsNormally); + expect(() => MetadataUtils.getNamedValue([]), returnsNormally); + }); + }); + + group('Method Signatures', () { + test('anyMeta should return bool', () { + final result = MetadataUtils.anyMeta([], 'singleton'); + expect(result, isA()); + }); + + test('getNamedValue should return String or null', () { + final result = MetadataUtils.getNamedValue([]); + expect(result, anyOf(isA(), isNull)); + }); + }); + + group('Edge Cases', () { + test('should handle various annotation names', () { + // Test with different annotation names + expect(MetadataUtils.anyMeta([], 'singleton'), isFalse); + expect(MetadataUtils.anyMeta([], 'provide'), isFalse); + expect(MetadataUtils.anyMeta([], 'instance'), isFalse); + expect(MetadataUtils.anyMeta([], 'named'), isFalse); + expect(MetadataUtils.anyMeta([], 'params'), isFalse); + }); + + test('should handle empty strings', () { + expect(MetadataUtils.anyMeta([], ''), isFalse); + expect(MetadataUtils.getNamedValue([]), isNull); + }); + }); + }); +} diff --git a/cherrypick_generator/test/module_generator_test.dart b/cherrypick_generator/test/module_generator_test.dart new file mode 100644 index 0000000..6fbeedc --- /dev/null +++ b/cherrypick_generator/test/module_generator_test.dart @@ -0,0 +1,648 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'package:test/test.dart'; +import 'package:build_test/build_test.dart'; +import 'package:build/build.dart'; + +import 'package:cherrypick_generator/module_generator.dart'; +import 'package:source_gen/source_gen.dart'; + +void main() { + group('ModuleGenerator Tests', () { + setUp(() { + // ModuleGenerator setup if needed + }); + + group('Simple Module Generation', () { + test('should generate basic module with instance binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @instance() + String testString() => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toInstance(testString()); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate basic module with provide binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @provide() + String testString() => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvide(() => testString()); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Singleton Bindings', () { + test('should generate singleton instance binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @instance() + @singleton() + String testString() => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toInstance(testString()).singleton(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate singleton provide binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @provide() + @singleton() + String testString() => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvide(() => testString()).singleton(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Named Bindings', () { + test('should generate named instance binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @instance() + @named('testName') + String testString() => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toInstance(testString()).withName('testName'); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate named singleton binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @provide() + @singleton() + @named('testName') + String testString() => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind() + .toProvide(() => testString()) + .withName('testName') + .singleton(); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Async Bindings', () { + test('should generate async instance binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @instance() + Future testString() async => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toInstanceAsync(testString()); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate async provide binding', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @provide() + Future testString() async => "Hello World"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvideAsync(() => testString()); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate async binding with params', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @provide() + Future testString(@params() dynamic params) async => "Hello \$params"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvideAsyncWithParams((args) => testString(args)); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Dependencies Injection', () { + test('should generate binding with injected dependencies', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +class ApiClient {} +class Repository {} + +@module() +abstract class TestModule extends Module { + @provide() + Repository repository(ApiClient client) => Repository(); +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvide( + () => repository(currentScope.resolve()), + ); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate binding with named dependencies', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +class ApiClient {} +class Repository {} + +@module() +abstract class TestModule extends Module { + @provide() + Repository repository(@named('api') ApiClient client) => Repository(); +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvide( + () => repository(currentScope.resolve(named: 'api')), + ); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Runtime Parameters', () { + test('should generate binding with params', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @provide() + String testString(@params() dynamic params) => "Hello \$params"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvideWithParams((args) => testString(args)); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + + test('should generate async binding with params', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @provide() + Future testString(@params() dynamic params) async => "Hello \$params"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toProvideAsyncWithParams((args) => testString(args)); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Complex Scenarios', () { + test('should generate module with multiple bindings', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +class ApiClient {} +class Repository {} + +@module() +abstract class TestModule extends Module { + @instance() + @singleton() + @named('baseUrl') + String baseUrl() => "https://api.example.com"; + + @provide() + @singleton() + ApiClient apiClient(@named('baseUrl') String url) => ApiClient(); + + @provide() + Repository repository(ApiClient client) => Repository(); + + @provide() + @named('greeting') + String greeting(@params() dynamic name) => "Hello \$name"; +} +'''; + + const expectedOutput = ''' +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_module.dart'; + +// ************************************************************************** +// ModuleGenerator +// ************************************************************************** + +final class \$TestModule extends TestModule { + @override + void builder(Scope currentScope) { + bind().toInstance(baseUrl()).withName('baseUrl').singleton(); + bind() + .toProvide( + () => apiClient(currentScope.resolve(named: 'baseUrl')), + ) + .singleton(); + bind().toProvide( + () => repository(currentScope.resolve()), + ); + bind() + .toProvideWithParams((args) => greeting(args)) + .withName('greeting'); + } +} +'''; + + await _testGeneration(input, expectedOutput); + }); + }); + + group('Error Cases', () { + test('should throw error for non-class element', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +void notAClass() {} +'''; + + await expectLater( + () => _testGeneration(input, ''), + throwsA(isA()), + ); + }); + + test('should throw error for method without @instance or @provide', + () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + String testString() => "Hello World"; +} +'''; + + await expectLater( + () => _testGeneration(input, ''), + throwsA(isA()), + ); + }); + + test('should throw error for @params with @instance', () async { + const input = ''' +import 'package:cherrypick_annotations/cherrypick_annotations.dart'; +import 'package:cherrypick/cherrypick.dart'; + +part 'test_module.module.cherrypick.g.dart'; + +@module() +abstract class TestModule extends Module { + @instance() + String testString(@params() dynamic params) => "Hello \$params"; +} +'''; + + await expectLater( + () => _testGeneration(input, ''), + throwsA(isA()), + ); + }); + }); + }); +} + +/// Helper function to test code generation +Future _testGeneration(String input, String expectedOutput) async { + await testBuilder( + moduleBuilder(BuilderOptions.empty), + { + 'a|lib/test_module.dart': input, + }, + outputs: { + 'a|lib/test_module.module.cherrypick.g.dart': expectedOutput, + }, + reader: await PackageAssetReader.currentIsolate(), + ); +} diff --git a/cherrypick_generator/test/simple_test.dart b/cherrypick_generator/test/simple_test.dart new file mode 100644 index 0000000..270056c --- /dev/null +++ b/cherrypick_generator/test/simple_test.dart @@ -0,0 +1,176 @@ +// +// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com) +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import 'package:cherrypick_generator/src/bind_spec.dart'; +import 'package:test/test.dart'; + +void main() { + group('Simple Generator Tests', () { + group('BindSpec', () { + test('should create BindSpec with correct properties', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + expect(bindSpec.returnType, equals('String')); + expect(bindSpec.methodName, equals('getString')); + expect(bindSpec.isSingleton, isFalse); + expect(bindSpec.bindingType, equals(BindingType.instance)); + }); + + test('should generate basic bind code', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains('bind()')); + expect(result, contains('toInstance')); + expect(result, contains('getString')); + }); + + test('should generate singleton bind code', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: true, + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains('singleton()')); + }); + + test('should generate named bind code', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + named: 'testName', + parameters: [], + bindingType: BindingType.instance, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains("withName('testName')")); + }); + + test('should generate provide bind code', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains('toProvide')); + expect(result, contains('() => getString')); + }); + + test('should generate async provide bind code', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: true, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains('toProvideAsync')); + }); + + test('should generate params bind code', () { + final bindSpec = BindSpec( + returnType: 'String', + methodName: 'getString', + isSingleton: false, + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: false, + hasParams: true, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains('toProvideWithParams')); + expect(result, contains('(args) => getString()')); + }); + + test('should generate complex bind with all options', () { + final bindSpec = BindSpec( + returnType: 'ApiClient', + methodName: 'createApiClient', + isSingleton: true, + named: 'mainApi', + parameters: [], + bindingType: BindingType.provide, + isAsyncInstance: false, + isAsyncProvide: true, + hasParams: false, + ); + + final result = bindSpec.generateBind(4); + expect(result, contains('bind()')); + expect(result, contains('toProvideAsync')); + expect(result, contains("withName('mainApi')")); + expect(result, contains('singleton()')); + }); + }); + + group('BindingType Enum', () { + test('should have correct values', () { + expect(BindingType.instance, isNotNull); + expect(BindingType.provide, isNotNull); + expect(BindingType.values.length, equals(2)); + }); + }); + + group('Generator Classes', () { + test('should be able to import generators', () { + // Test that we can import the generator classes + expect(BindSpec, isNotNull); + expect(BindingType, isNotNull); + }); + }); + }); +} diff --git a/examples/client_app/pubspec.lock b/examples/client_app/pubspec.lock index 730bb34..10f7075 100644 --- a/examples/client_app/pubspec.lock +++ b/examples/client_app/pubspec.lock @@ -148,7 +148,7 @@ packages: path: "../../cherrypick_generator" relative: true source: path - version: "1.1.0-dev.3" + version: "1.1.0-dev.5" clock: dependency: transitive description: diff --git a/examples/client_app/pubspec.yaml b/examples/client_app/pubspec.yaml index 72f5b30..dbaf184 100644 --- a/examples/client_app/pubspec.yaml +++ b/examples/client_app/pubspec.yaml @@ -11,10 +11,13 @@ environment: dependencies: flutter: sdk: flutter - cherrypick: ^2.2.0-dev.1 - cherrypick_flutter: ^1.1.2-dev.1 + cherrypick: + path: ../../cherrypick + cherrypick_flutter: + path: ../../cherrypick_flutter - cherrypick_annotations: ^1.1.0-dev.1 + cherrypick_annotations: + path: ../../cherrypick_annotations cupertino_icons: ^1.0.8 @@ -24,7 +27,8 @@ dev_dependencies: flutter_lints: ^5.0.0 - cherrypick_generator: ^1.1.0-dev.5 + cherrypick_generator: + path: ../../cherrypick_generator build_runner: ^2.4.15 # For information on the generic Dart part of this file, see the diff --git a/examples/postly/analysis_options.yaml b/examples/postly/analysis_options.yaml index 0d29021..af1e265 100644 --- a/examples/postly/analysis_options.yaml +++ b/examples/postly/analysis_options.yaml @@ -9,6 +9,13 @@ # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml +analyzer: + exclude: + - "**/*.g.dart" + - "**/*.freezed.dart" + - "**/*.gr.dart" + - "**/*.config.dart" + linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` diff --git a/examples/postly/pubspec.lock b/examples/postly/pubspec.lock index 2d064d5..df81ea0 100644 --- a/examples/postly/pubspec.lock +++ b/examples/postly/pubspec.lock @@ -165,7 +165,7 @@ packages: path: "../../cherrypick_generator" relative: true source: path - version: "1.1.0-dev.4" + version: "1.1.0-dev.5" clock: dependency: transitive description: diff --git a/examples/postly/pubspec.yaml b/examples/postly/pubspec.yaml index 4ccfce3..b2d7851 100644 --- a/examples/postly/pubspec.yaml +++ b/examples/postly/pubspec.yaml @@ -12,8 +12,10 @@ dependencies: flutter: sdk: flutter - cherrypick: ^2.2.0-dev.1 - cherrypick_annotations: ^1.1.0-dev.1 + cherrypick: + path: ../../cherrypick + cherrypick_annotations: + path: ../../cherrypick_annotations dio: ^5.4.0 retrofit: ^4.0.3 @@ -30,7 +32,8 @@ dev_dependencies: flutter_lints: ^5.0.0 - cherrypick_generator: ^1.1.0-dev.5 + cherrypick_generator: + path: ../../cherrypick_generator build_runner: 2.4.15 retrofit_generator: ^9.1.5 diff --git a/melos.yaml b/melos.yaml index 4d7784e..5d77fa0 100644 --- a/melos.yaml +++ b/melos.yaml @@ -18,7 +18,23 @@ scripts: exec: dart format lib test: - exec: flutter test + run: | + echo "Running Dart tests..." + melos exec --scope="cherrypick,cherrypick_annotations,cherrypick_generator" -- dart test --reporter=compact + echo "Running Flutter tests..." + melos exec --scope="cherrypick_flutter" -- flutter test --reporter=compact + + test:dart: + description: "Run tests for Dart packages only" + exec: dart test --reporter=compact + packageFilters: + scope: ["cherrypick", "cherrypick_annotations", "cherrypick_generator"] + + test:flutter: + description: "Run tests for Flutter packages only" + exec: flutter test --reporter=compact + packageFilters: + scope: ["cherrypick_flutter"] codegen: run: | @@ -31,4 +47,4 @@ scripts: exec: dart pub upgrade --major-versions drop: - exec: flutter clean + exec: flutter clean