diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3167c7ef..013d2942 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,10 +32,8 @@ jobs: if: startsWith(matrix.os, 'windows') run: echo "%LOCALAPPDATA%\Pub\Cache\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Get dependencies - run: | - dart pub global activate melos - melos bootstrap + - name: Prepare workspace + uses: bluefireteam/melos-action@v3 - name: Generate test data and see if changes run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bafd27cc..00a4948b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - uses: flutter-actions/setup-flutter@v4.0 - name: Install dependencies run: dart pub get - - name: Prepare Melos workspace + - name: Prepare workspace uses: bluefireteam/melos-action@v3 - name: Publish - run: melos run publish:force --no-select + run: melos publish:force --no-select diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e36bff9..4f3bab04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,10 +39,8 @@ jobs: - name: Set environment run: echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH - - name: Get dependencies - run: | - dart pub global activate melos - melos bootstrap + - name: Prepare workspace + uses: bluefireteam/melos-action@v3 - name: Running build run: | diff --git a/melos.yaml b/melos.yaml index 302ce455..caf2d912 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,9 +1,11 @@ name: FlutterGen packages: - - packages/** - - integrations/** - - examples/** + - packages/command + - packages/core + - packages/runner + - examples/example + - examples/example_resources ide: intellij: @@ -41,7 +43,7 @@ scripts: description: flutter build apk gen:build_runner: - exec: dart run build_runner build --delete-conflicting-outputs + exec: dart run build_runner build -d packageFilters: ignore: - example @@ -58,14 +60,14 @@ scripts: description: dart ../../packages/command/bin/flutter_gen_command.dart gen:examples:build_runner: - run: flutter pub run build_runner build --delete-conflicting-outputs + run: dart run build_runner build -d exec: concurrency: 1 packageFilters: scope: - example - example_resources - description: flutter pub run build_runner build --delete-conflicting-outputs + description: dart run build_runner build --delete-conflicting-outputs gen:actual_data: run: dart packages/core/scripts/generate_facts.dart diff --git a/package.json b/package.json index 210d0c57..ccff1b23 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "author": "wasabeef", "scripts": { - "format": "prettier --config prettier.config.js --write '**/*.+(md|yml|yaml|json)'", + "format": "prettier --config prettier.config.js --write \"**/*.+(md|yml|yaml|json)\"", "prepare": "husky install" }, "lint-staged": { @@ -21,5 +21,5 @@ "prettier": "^3.0.3", "prettier-plugin-packagejson": "^2.4.5" }, - "packageManager": "pnpm@10.22.0" + "packageManager": "pnpm@10.30.3" } diff --git a/packages/core/lib/generators/integrations/rive_integration.dart b/packages/core/lib/generators/integrations/rive_integration.dart index 9a8ff6af..fde44462 100644 --- a/packages/core/lib/generators/integrations/rive_integration.dart +++ b/packages/core/lib/generators/integrations/rive_integration.dart @@ -1,10 +1,9 @@ import 'package:flutter_gen_core/generators/integrations/integration.dart'; -import 'package:pub_semver/pub_semver.dart' show Version, VersionConstraint; - -typedef RiveIntegrationLatest = RiveIntegration0140; +import 'package:pub_semver/pub_semver.dart' + show Version, VersionConstraint, VersionRange; /// Create Rive integration based on the resolved version. -abstract final class RiveIntegration extends Integration { +sealed class RiveIntegration extends Integration { factory RiveIntegration( String packageName, { Version? resolvedVersion, @@ -12,32 +11,39 @@ abstract final class RiveIntegration extends Integration { }) { // Resolve integration by version. RiveIntegration? integration = switch (resolvedVersion) { - final v? when v < Version(0, 14, 0) => + final v? when v < RiveIntegration0140.minimumSupportVersion => RiveIntegrationClassic(packageName), - Version() => RiveIntegrationLatest(packageName), + Version() => RiveIntegration0140(packageName), null => null, }; // Resolve integration by version constraint. integration ??= switch (resolvedVersionConstraint) { - final c? when c.allows(Version(0, 14, 0)) => - RiveIntegrationLatest(packageName), + final VersionRange c => switch (c.max) { + final max? when max == RiveIntegration0140.minimumSupportVersion => + c.includeMax + ? RiveIntegration0140(packageName) + : RiveIntegrationClassic(packageName), + final max? when max > RiveIntegration0140.minimumSupportVersion => + RiveIntegration0140(packageName), + _ => RiveIntegrationClassic(packageName), + }, VersionConstraint() => RiveIntegrationClassic(packageName), null => null, }; // Use the latest integration as the fallback. - integration ??= RiveIntegrationLatest(packageName); + integration ??= RiveIntegration0140(packageName); return integration; } RiveIntegration._(String packageName) : super(packageName); -} -/// Rive integration for versions before 0.14.0. -final class RiveIntegrationClassic extends RiveIntegration { - RiveIntegrationClassic(String packageName) : super._(packageName); + String get _classDefinition; + + @override + String get classOutput => _classDefinition; String? get packageExpression => isPackage ? 'packages/$packageName/' : null; @@ -48,8 +54,20 @@ final class RiveIntegrationClassic extends RiveIntegration { ]; @override - String get classOutput => _classDefinition; + String get className => 'RiveGenImage'; + + @override + bool isSupport(AssetType asset) => asset.extension == '.riv'; + + @override + bool get isConstConstructor => true; +} + +/// Rive integration for versions before 0.14.0. +final class RiveIntegrationClassic extends RiveIntegration { + RiveIntegrationClassic(String packageName) : super._(packageName); + @override String get _classDefinition => '''class RiveGenImage { const RiveGenImage( this._assetName, { @@ -92,20 +110,13 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''} String get keyName => ${isPackage ? '\'$packageExpression\$_assetName\'' : '_assetName'}; }'''; - - @override - String get className => 'RiveGenImage'; - - @override - bool isSupport(AssetType asset) => asset.extension == '.riv'; - - @override - bool get isConstConstructor => true; } /// Rive integration for versions equal to or above 0.14.0. -final class RiveIntegration0140 extends RiveIntegrationClassic { - RiveIntegration0140(String packageName) : super(packageName); +final class RiveIntegration0140 extends RiveIntegration { + RiveIntegration0140(String packageName) : super._(packageName); + + static final minimumSupportVersion = Version(0, 14, 0, pre: '0'); @override String get _classDefinition => '''class RiveGenImage { diff --git a/packages/core/lib/settings/config.dart b/packages/core/lib/settings/config.dart index 68cacd18..655b8e18 100644 --- a/packages/core/lib/settings/config.dart +++ b/packages/core/lib/settings/config.dart @@ -10,6 +10,8 @@ import 'package:flutter_gen_core/utils/error.dart'; import 'package:flutter_gen_core/utils/log.dart'; import 'package:flutter_gen_core/utils/map.dart'; import 'package:flutter_gen_core/version.gen.dart'; +import 'package:json_annotation/json_annotation.dart' + show CheckedFromJsonException; import 'package:path/path.dart'; import 'package:pub_semver/pub_semver.dart' show VersionConstraint, Version; import 'package:yaml/yaml.dart'; @@ -105,7 +107,7 @@ Config loadPubspecConfig(File pubspecFile, {File? buildFile}) { final pubspec = Pubspec.fromJson(mergedMap); final pubspecLockFile = File( - normalize(join(basename(pubspecFile.parent.path), 'pubspec.lock')), + normalize(join(pubspecFile.parent.path, 'pubspec.lock')), ); final pubspecLockContent = switch (pubspecLockFile.existsSync()) { true => pubspecLockFile.readAsStringSync(), @@ -130,7 +132,7 @@ Config loadPubspecConfig(File pubspecFile, {File? buildFile}) { } final analysisOptionsFile = File( - normalize(join(basename(pubspecFile.parent.path), 'analysis_options.yaml')), + normalize(join(pubspecFile.parent.path, 'analysis_options.yaml')), ); final analysisOptionsContent = switch (analysisOptionsFile.existsSync()) { true => analysisOptionsFile.readAsStringSync(), @@ -165,6 +167,8 @@ Config? loadPubspecConfigOrNull(File pubspecFile, {File? buildFile}) { log.severe('File system error when reading files.', e, s); } on InvalidSettingsException catch (e, s) { log.severe('Invalid settings in files.', e, s); + } on CheckedFromJsonException catch (e, s) { + log.severe('Invalid settings in files.', e, s); } return null; } diff --git a/packages/core/scripts/generate_facts.dart b/packages/core/scripts/generate_facts.dart index 9c7dbf38..02dfc545 100644 --- a/packages/core/scripts/generate_facts.dart +++ b/packages/core/scripts/generate_facts.dart @@ -8,9 +8,8 @@ late final Directory dir; void main() async { dir = File.fromUri(Platform.script).parent.parent.directory('test_resources'); - final configFiles = dir.listSync().whereType().where( - (e) => e.extension == '.yaml', - ); + final configFiles = + dir.listSync().whereType().where((e) => e.extension == '.yaml'); for (final file in configFiles) { final File pubspecFile; final File? buildFile; diff --git a/packages/core/test/assets_gen_integrations_test.dart b/packages/core/test/assets_gen_integrations_test.dart index dd397d7e..e07560f3 100644 --- a/packages/core/test/assets_gen_integrations_test.dart +++ b/packages/core/test/assets_gen_integrations_test.dart @@ -317,14 +317,20 @@ void main() { resolvedVersionConstraint: VersionConstraint.parse('^0.14.0'), ); expect(fallbackIntegration, isA()); - expect(fallbackIntegration.classOutput.contains('riveFileLoader({'), isTrue); + expect( + fallbackIntegration.classOutput.contains('riveFileLoader({'), + isTrue, + ); }); test('RiveIntegration fallback behavior', () { // Test with no version information (should return RiveIntegration0140 as fallback) final fallbackIntegration = RiveIntegration(''); expect(fallbackIntegration, isA()); - expect(fallbackIntegration.classOutput.contains('riveFileLoader({'), isTrue); + expect( + fallbackIntegration.classOutput.contains('riveFileLoader({'), + isTrue, + ); }); test('RiveIntegrationClassic classOutput structure', () { @@ -359,7 +365,7 @@ void main() { isTrue, ); expect( - output.contains("'packages/test_package/\$_assetName'"), + output.contains(r"'packages/test_package/$_assetName'"), isTrue, ); }); @@ -376,7 +382,10 @@ void main() { expect(output.contains('_rive.FileLoader riveFileLoader({'), isTrue); expect(output.contains('_rive.FileLoader.fromAsset'), isTrue); expect(output.contains('_rive.Factory? factory,'), isTrue); - expect(output.contains('riveFactory: factory ?? _rive.Factory.rive'), isTrue); + expect( + output.contains('riveFactory: factory ?? _rive.Factory.rive'), + isTrue, + ); expect(output.contains('String get path =>'), isTrue); expect(output.contains('String get keyName =>'), isTrue); @@ -398,7 +407,7 @@ void main() { isTrue, ); expect( - output.contains("'packages/test_package/\$_assetName'"), + output.contains(r"'packages/test_package/$_assetName'"), isTrue, ); }); @@ -459,5 +468,86 @@ void main() { isTrue, ); }); + + test('Integration version resolution in AssetsGenConfig', () { + // Test that version resolution is properly integrated into assets generation + // This validates the full flow from config loading to integration creation. + // Create a RiveIntegration with both resolved version and constraint + // When resolvedVersion is 0.13.0 (< 0.14.0), it should use Classic + // even if resolvedVersionConstraint would allow 0.14.0. + final integrationWithBoth = RiveIntegration( + '', + resolvedVersion: Version(0, 13, 0), + resolvedVersionConstraint: VersionConstraint.parse('^0.14.0'), + ); + // resolvedVersion takes priority: 0.13.0 < 0.14.0 => Classic + expect(integrationWithBoth, isA()); + + // Create a RiveIntegration with only resolvedVersion + final integrationVersionOnly = RiveIntegration( + '', + resolvedVersion: Version(0, 14, 0), + ); + expect(integrationVersionOnly, isA()); + + // Create a RiveIntegration with only resolvedVersionConstraint + final integrationConstraintOnly = RiveIntegration( + '', + resolvedVersionConstraint: VersionConstraint.parse('^0.14.0'), + ); + expect(integrationConstraintOnly, isA()); + }); + + test('Version resolution with various constraint formats', () { + // Test with ^0.13.x constraint (should use Classic) + final caretOld = RiveIntegration( + '', + resolvedVersionConstraint: VersionConstraint.parse('^0.13.5'), + ); + expect(caretOld, isA()); + + // Test with ^0.14.x constraint (should use 0140) + final caretNew = RiveIntegration( + '', + resolvedVersionConstraint: VersionConstraint.parse('^0.14.1'), + ); + expect(caretNew, isA()); + + // Test with >= constraint that includes 0.14.0 + final rangeIncludesNew = RiveIntegration( + '', + resolvedVersionConstraint: VersionConstraint.parse('>=0.13.0 <1.0.0'), + ); + expect(rangeIncludesNew, isA()); + + // Test with >= constraint that includes the minimum pre-release. + final rangeIncludesPre = RiveIntegration( + '', + resolvedVersionConstraint: + VersionConstraint.parse('>=0.13.0 <=0.14.0-0'), + ); + expect(rangeIncludesPre, isA()); + + // Test with >= constraint that excludes 0.14.0 + final rangeExcludesNew = RiveIntegration( + '', + resolvedVersionConstraint: VersionConstraint.parse('>=0.13.0 <0.14.0'), + ); + expect(rangeExcludesNew, isA()); + + // Test with exact version < 0.14.0 + final exactOld = RiveIntegration( + '', + resolvedVersion: Version(0, 13, 99), + ); + expect(exactOld, isA()); + + // Test with exact version >= 0.14.0 + final exactNew = RiveIntegration( + '', + resolvedVersion: Version(0, 14, 0), + ); + expect(exactNew, isA()); + }); }); } diff --git a/packages/core/test/config_test.dart b/packages/core/test/config_test.dart new file mode 100644 index 00000000..fb375f1d --- /dev/null +++ b/packages/core/test/config_test.dart @@ -0,0 +1,231 @@ +import 'dart:io'; + +import 'package:flutter_gen_core/generators/integrations/lottie_integration.dart'; +import 'package:flutter_gen_core/generators/integrations/rive_integration.dart'; +import 'package:flutter_gen_core/generators/integrations/svg_integration.dart'; +import 'package:flutter_gen_core/generators/registry.dart'; +import 'package:flutter_gen_core/settings/config.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +void main() { + group('Config integration version resolution', () { + test('resolves versions from pubspec.lock', () { + final pubspecFile = File( + 'test_resources/integration_versions/pubspec.yaml', + ); + final config = loadPubspecConfig(pubspecFile); + + // Check that versions are resolved from pubspec.lock + expect( + config.integrationResolvedVersions[RiveIntegration], + equals(Version(0, 13, 5)), + ); + expect( + config.integrationResolvedVersions[SvgIntegration], + equals(Version(2, 0, 9)), + ); + expect( + config.integrationResolvedVersions[LottieIntegration], + equals(Version(5, 1, 0)), + ); + }); + + test('resolves version constraints from pubspec.yaml', () { + final pubspecFile = File( + 'test_resources/integration_versions/pubspec.yaml', + ); + final config = loadPubspecConfig(pubspecFile); + + // Check that constraints are resolved from pubspec.yaml + expect( + config.integrationVersionConstraints[RiveIntegration], + equals(VersionConstraint.parse('^0.13.0')), + ); + expect( + config.integrationVersionConstraints[SvgIntegration], + equals(VersionConstraint.parse('^2.0.0')), + ); + expect( + config.integrationVersionConstraints[LottieIntegration], + equals(VersionConstraint.parse('^5.0.0')), + ); + }); + + test('resolves Rive 0.14.0 versions correctly', () { + final pubspecFile = File( + 'test_resources/integration_versions_rive_014/pubspec.yaml', + ); + final config = loadPubspecConfig(pubspecFile); + + // Check that Rive 0.14.0+ version is resolved + expect( + config.integrationResolvedVersions[RiveIntegration], + equals(Version(0, 14, 1)), + ); + expect( + config.integrationVersionConstraints[RiveIntegration], + equals(VersionConstraint.parse('^0.14.0')), + ); + + // Check that flutter_svg is also resolved + expect( + config.integrationResolvedVersions[SvgIntegration], + equals(Version(2, 0, 10)), + ); + expect( + config.integrationVersionConstraints[SvgIntegration], + equals(VersionConstraint.parse('>=2.0.0 <3.0.0')), + ); + }); + + test('handles missing pubspec.lock gracefully', () { + // Use a pubspec without a corresponding .lock file + final pubspecFile = File( + 'test_resources/pubspec_assets_rive_integrations.yaml', + ); + final config = loadPubspecConfig(pubspecFile); + + // Should have empty or no resolved versions + // but should not crash + expect(config, isNotNull); + expect(config.integrationResolvedVersions, isA()); + expect(config.integrationVersionConstraints, isA()); + }); + + test('verifies only expected integration types are present', () { + final pubspecFile = File( + 'test_resources/integration_versions/pubspec.yaml', + ); + final config = loadPubspecConfig(pubspecFile); + + // Verify that only integration types from the registry are in the maps + final expectedTypes = integrationPackages.keys.toList(); + + for (final key in config.integrationVersionConstraints.keys) { + expect( + expectedTypes.contains(key), + isTrue, + reason: 'Unexpected integration type: $key', + ); + } + + for (final key in config.integrationResolvedVersions.keys) { + expect( + expectedTypes.contains(key), + isTrue, + reason: 'Unexpected integration type: $key', + ); + } + }); + + test('integration versions are used in AssetsGenConfig', () { + final pubspecFile = File( + 'test_resources/integration_versions/pubspec.yaml', + ); + final config = loadPubspecConfig(pubspecFile); + + // Verify that the resolved versions and constraints are available + expect(config.integrationResolvedVersions, isNotEmpty); + expect(config.integrationVersionConstraints, isNotEmpty); + + // Verify they can be passed to generators + expect( + config.integrationResolvedVersions[RiveIntegration], + isA(), + ); + expect( + config.integrationVersionConstraints[RiveIntegration], + isA(), + ); + }); + + test('version resolution with only constraint and no lock', () { + // Create a temporary pubspec file without a lock + final tempDir = Directory.systemTemp.createTempSync('flutter_gen_test'); + try { + final tempPubspec = File('${tempDir.path}/pubspec.yaml'); + tempPubspec.writeAsStringSync(''' +name: test_no_lock +environment: + sdk: ^3.0.0 +dependencies: + rive: ^0.13.0 +flutter_gen: + output: lib/gen/ + integrations: + rive: true +flutter: + assets: + - assets/ +'''); + + final config = loadPubspecConfig(tempPubspec); + + // Should have constraint but no resolved version + expect( + config.integrationVersionConstraints[RiveIntegration], + equals(VersionConstraint.parse('^0.13.0')), + ); + expect( + config.integrationResolvedVersions[RiveIntegration], + isNull, + ); + } finally { + tempDir.deleteSync(recursive: true); + } + }); + + test('version resolution with lock but no constraint', () { + // This tests the case where pubspec.lock has a version + // but pubspec.yaml doesn't specify a constraint (e.g., path dependency) + final tempDir = Directory.systemTemp.createTempSync('flutter_gen_test'); + try { + final tempPubspec = File('${tempDir.path}/pubspec.yaml'); + tempPubspec.writeAsStringSync(''' +name: test_lock_only +environment: + sdk: ^3.0.0 +dependencies: + rive: + path: ../rive +flutter_gen: + output: lib/gen/ + integrations: + rive: true +flutter: + assets: + - assets/ +'''); + + final tempLock = File('${tempDir.path}/pubspec.lock'); + tempLock.writeAsStringSync(''' +packages: + rive: + dependency: "direct main" + description: + path: "../rive" + relative: true + source: path + version: "0.13.5" +sdks: + dart: ">=3.0.0 <4.0.0" +'''); + + final config = loadPubspecConfig(tempPubspec); + + // Should have resolved version but no constraint + expect( + config.integrationResolvedVersions[RiveIntegration], + equals(Version(0, 13, 5)), + ); + expect( + config.integrationVersionConstraints[RiveIntegration], + isNull, + ); + } finally { + tempDir.deleteSync(recursive: true); + } + }); + }); +} diff --git a/packages/core/test/flutter_gen_test.dart b/packages/core/test/flutter_gen_test.dart index e28b3e0d..c3814a26 100644 --- a/packages/core/test/flutter_gen_test.dart +++ b/packages/core/test/flutter_gen_test.dart @@ -172,11 +172,14 @@ void main() { test('Wrong lineLength', () async { const pubspec = 'test_resources/pubspec_wrong_line_length.yaml'; - - expect( - () => FlutterGenerator(File(pubspec)).build(), - throwsA(isA()), - ); + const assets = 'pubspec_wrong_line_length_assets.gen.dart'; + const colors = 'pubspec_wrong_line_length_colors.gen.dart'; + const fonts = 'pubspec_wrong_line_length_fonts.gen.dart'; + + await FlutterGenerator(File(pubspec)).build(); + expect(File('test_resources/lib/gen/$assets').existsSync(), false); + expect(File('test_resources/lib/gen/$fonts').existsSync(), false); + expect(File('test_resources/lib/gen/$colors').existsSync(), false); }); test('Disabled generation', () async { diff --git a/packages/core/test/settings_test.dart b/packages/core/test/settings_test.dart index 3629b1f4..0d5b6cf3 100644 --- a/packages/core/test/settings_test.dart +++ b/packages/core/test/settings_test.dart @@ -1,10 +1,15 @@ import 'package:collection/collection.dart'; import 'package:flutter_gen_core/settings/asset_type.dart'; +import 'package:flutter_gen_core/settings/config_default.dart' + show configDefaultYamlContent; import 'package:flutter_gen_core/settings/flavored_asset.dart'; import 'package:flutter_gen_core/settings/pubspec.dart'; import 'package:flutter_gen_core/utils/error.dart' show InvalidSettingsException; +import 'package:flutter_gen_core/utils/map.dart' show mergeMap; +import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; void main() { group(AssetType, () { @@ -116,4 +121,238 @@ void main() { ); }); }); + + group('Pubspec.dependenciesVersionConstraint', () { + final defaultMap = loadYaml(configDefaultYamlContent) as YamlMap?; + test('parses string version constraints', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +dependencies: + rive: ^0.13.0 + flutter_svg: ^2.0.0 + lottie: ">=1.0.0 <2.0.0" +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + expect(pubspec.dependenciesVersionConstraint['rive'], isNotNull); + expect( + pubspec.dependenciesVersionConstraint['rive'], + equals(VersionConstraint.parse('^0.13.0')), + ); + + expect(pubspec.dependenciesVersionConstraint['flutter_svg'], isNotNull); + expect( + pubspec.dependenciesVersionConstraint['flutter_svg'], + equals(VersionConstraint.parse('^2.0.0')), + ); + + expect(pubspec.dependenciesVersionConstraint['lottie'], isNotNull); + expect( + pubspec.dependenciesVersionConstraint['lottie'], + equals(VersionConstraint.parse('>=1.0.0 <2.0.0')), + ); + }); + + test('parses map with version key', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +dependencies: + rive: + version: ^0.14.0 + flutter_svg: + version: ">=2.0.0 <3.0.0" +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + expect(pubspec.dependenciesVersionConstraint['rive'], isNotNull); + expect( + pubspec.dependenciesVersionConstraint['rive'], + equals(VersionConstraint.parse('^0.14.0')), + ); + + expect(pubspec.dependenciesVersionConstraint['flutter_svg'], isNotNull); + expect( + pubspec.dependenciesVersionConstraint['flutter_svg'], + equals(VersionConstraint.parse('>=2.0.0 <3.0.0')), + ); + }); + + test('handles path dependencies without version', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +dependencies: + rive: + path: ../rive + flutter_svg: ^2.0.0 +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + // Path dependency should be null since no version constraint + expect(pubspec.dependenciesVersionConstraint['rive'], isNull); + + // String version should still work + expect(pubspec.dependenciesVersionConstraint['flutter_svg'], isNotNull); + expect( + pubspec.dependenciesVersionConstraint['flutter_svg'], + equals(VersionConstraint.parse('^2.0.0')), + ); + }); + + test('handles git dependencies without version', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +dependencies: + rive: + git: + url: https://github.com/example/rive.git + ref: main + lottie: ^5.0.0 +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + // Git dependency should be null since no version constraint + expect(pubspec.dependenciesVersionConstraint['rive'], isNull); + + // String version should still work + expect(pubspec.dependenciesVersionConstraint['lottie'], isNotNull); + expect( + pubspec.dependenciesVersionConstraint['lottie'], + equals(VersionConstraint.parse('^5.0.0')), + ); + }); + + test('handles invalid version strings gracefully', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +dependencies: + rive: invalid_version + flutter_svg: ^2.0.0 +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + // Invalid version should be null + expect(pubspec.dependenciesVersionConstraint['rive'], isNull); + + // Valid version should work + expect(pubspec.dependenciesVersionConstraint['flutter_svg'], isNotNull); + }); + + test('handles dependencies with invalid version', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +dependencies: + rive: + version: invalid_version + lottie: ^5.0.0 +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + // Invalid version in map should be null + expect(pubspec.dependenciesVersionConstraint['rive'], isNull); + + // Valid string version should work + expect(pubspec.dependenciesVersionConstraint['lottie'], isNotNull); + }); + + test('handles null dependencies', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + // Should return empty map for null dependencies + expect(pubspec.dependenciesVersionConstraint, isEmpty); + }); + + test('handles mixed dependency formats', () { + Map yaml = loadYaml(''' +name: test +environment: + sdk: ^3.0.0 +dependencies: + rive: ^0.13.0 + flutter_svg: + version: ^2.0.0 + lottie: + path: ../lottie + another_package: + git: + url: https://github.com/example/package.git +flutter_gen: + output: lib/gen/ +flutter: + assets: + - assets/ +'''); + yaml = mergeMap([defaultMap, yaml]); + final pubspec = Pubspec.fromJson(yaml); + + expect( + pubspec.dependenciesVersionConstraint['rive'], + equals(VersionConstraint.parse('^0.13.0')), + ); + expect( + pubspec.dependenciesVersionConstraint['flutter_svg'], + equals(VersionConstraint.parse('^2.0.0')), + ); + expect(pubspec.dependenciesVersionConstraint['lottie'], isNull); + expect(pubspec.dependenciesVersionConstraint['another_package'], isNull); + }); + }); } diff --git a/packages/core/test_resources/integration_versions/pubspec.lock b/packages/core/test_resources/integration_versions/pubspec.lock new file mode 100644 index 00000000..e2f8a807 --- /dev/null +++ b/packages/core/test_resources/integration_versions/pubspec.lock @@ -0,0 +1,29 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + rive: + dependency: "direct main" + description: + name: rive + sha256: "abc123" + url: "https://pub.dev" + source: hosted + version: "0.13.5" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "def456" + url: "https://pub.dev" + source: hosted + version: "2.0.9" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "ghi789" + url: "https://pub.dev" + source: hosted + version: "5.1.0" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/packages/core/test_resources/integration_versions/pubspec.yaml b/packages/core/test_resources/integration_versions/pubspec.yaml new file mode 100644 index 00000000..fecd6e60 --- /dev/null +++ b/packages/core/test_resources/integration_versions/pubspec.yaml @@ -0,0 +1,20 @@ +name: test_integration_versions + +environment: + sdk: ^3.0.0 + +dependencies: + rive: ^0.13.0 + flutter_svg: ^2.0.0 + lottie: ^5.0.0 + +flutter_gen: + output: lib/gen/ + integrations: + rive: true + flutter_svg: true + lottie: true + +flutter: + assets: + - assets/ diff --git a/packages/core/test_resources/integration_versions_rive_014/pubspec.lock b/packages/core/test_resources/integration_versions_rive_014/pubspec.lock new file mode 100644 index 00000000..8009d48b --- /dev/null +++ b/packages/core/test_resources/integration_versions_rive_014/pubspec.lock @@ -0,0 +1,21 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + rive: + dependency: "direct main" + description: + name: rive + sha256: "xyz123" + url: "https://pub.dev" + source: hosted + version: "0.14.1" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "uvw456" + url: "https://pub.dev" + source: hosted + version: "2.0.10" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/packages/core/test_resources/integration_versions_rive_014/pubspec.yaml b/packages/core/test_resources/integration_versions_rive_014/pubspec.yaml new file mode 100644 index 00000000..5c19213b --- /dev/null +++ b/packages/core/test_resources/integration_versions_rive_014/pubspec.yaml @@ -0,0 +1,18 @@ +name: test_integration_versions_rive_014 + +environment: + sdk: ^3.0.0 + +dependencies: + rive: ^0.14.0 + flutter_svg: '>=2.0.0 <3.0.0' + +flutter_gen: + output: lib/gen/ + integrations: + rive: true + flutter_svg: true + +flutter: + assets: + - assets/