// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:io'; import 'package:pub_semver/pub_semver.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; void main() { group('hosted', _hostedDependency); group('git', _gitDependency); group('sdk', _sdkDependency); group('path', _pathDependency); group('errors', () { test('List', () { _expectThrows( [], r''' line 4, column 10: Unsupported value for "dep". Not a valid dependency value. ╷ 4 │ "dep": [] │ ^^ ╵''', ); }); test('int', () { _expectThrows( 42, r''' line 4, column 10: Unsupported value for "dep". Not a valid dependency value. ╷ 4 │ "dep": 42 │ ┌──────────^ 5 │ │ } │ └─^ ╵''', ); }); test('map with too many keys', () { _expectThrows( {'path': 'a', 'git': 'b'}, r''' line 6, column 11: Unsupported value for "git". A dependency may only have one source. ╷ 6 │ "git": "b" │ ^^^ ╵''', ); }); test('map with unsupported keys', () { _expectThrows( {'bob': 'a', 'jones': 'b'}, r''' line 5, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted] ╷ 5 │ "bob": "a", │ ^^^^^ ╵''', ); }); }); } void _hostedDependency() { test('null', () async { final dep = await _dependency(null); expect(dep.version.toString(), 'any'); expect(dep.hosted, isNull); expect(dep.toString(), 'HostedDependency: any'); }); test('empty map', () async { final dep = await _dependency({}); expect(dep.hosted, isNull); expect(dep.toString(), 'HostedDependency: any'); }); test('string version', () async { final dep = await _dependency('^1.0.0'); expect(dep.version.toString(), '^1.0.0'); expect(dep.hosted, isNull); expect(dep.toString(), 'HostedDependency: ^1.0.0'); }); test('bad string version', () { _expectThrows( 'not a version', r''' line 4, column 10: Unsupported value for "dep". Could not parse version "not a version". Unknown text at "not a version". ╷ 4 │ "dep": "not a version" │ ^^^^^^^^^^^^^^^ ╵''', ); }); test('map w/ just version', () async { final dep = await _dependency({'version': '^1.0.0'}); expect(dep.version.toString(), '^1.0.0'); expect(dep.hosted, isNull); expect(dep.toString(), 'HostedDependency: ^1.0.0'); }); test('map w/ version and hosted as Map', () async { final dep = await _dependency({ 'version': '^1.0.0', 'hosted': {'name': 'hosted_name', 'url': 'https://hosted_url'}, }); expect(dep.version.toString(), '^1.0.0'); expect(dep.hosted!.name, 'hosted_name'); expect(dep.hosted!.url.toString(), 'https://hosted_url'); expect(dep.toString(), 'HostedDependency: ^1.0.0'); }); test('map /w hosted as a map without name', () async { final dep = await _dependency( { 'version': '^1.0.0', 'hosted': {'url': 'https://hosted_url'}, }, skipTryPub: true, // todo: Unskip once pub supports this syntax ); expect(dep.version.toString(), '^1.0.0'); expect(dep.hosted!.declaredName, isNull); expect(dep.hosted!.name, 'dep'); expect(dep.hosted!.url.toString(), 'https://hosted_url'); expect(dep.toString(), 'HostedDependency: ^1.0.0'); }); test('map w/ bad version value', () { _expectThrows( { 'version': 'not a version', 'hosted': {'name': 'hosted_name', 'url': 'hosted_url'}, }, r''' line 5, column 15: Unsupported value for "version". Could not parse version "not a version". Unknown text at "not a version". ╷ 5 │ "version": "not a version", │ ^^^^^^^^^^^^^^^ ╵''', ); }); test('map w/ extra keys should fail', () { _expectThrows( { 'version': '^1.0.0', 'hosted': {'name': 'hosted_name', 'url': 'hosted_url'}, 'not_supported': null, }, r''' line 10, column 4: Unrecognized keys: [not_supported]; supported keys: [sdk, git, path, hosted] ╷ 10 │ "not_supported": null │ ^^^^^^^^^^^^^^^ ╵''', ); }); test('map w/ version and hosted as String', () async { final dep = await _dependency( {'version': '^1.0.0', 'hosted': 'hosted_url'}, skipTryPub: true, // todo: Unskip once put supports this ); expect(dep.version.toString(), '^1.0.0'); expect(dep.hosted!.declaredName, isNull); expect(dep.hosted!.name, 'dep'); expect(dep.hosted!.url, Uri.parse('hosted_url')); expect(dep.toString(), 'HostedDependency: ^1.0.0'); }); test('map w/ hosted as String', () async { final dep = await _dependency({'hosted': 'hosted_url'}); expect(dep.version, VersionConstraint.any); expect(dep.hosted!.declaredName, isNull); expect(dep.hosted!.name, 'dep'); expect(dep.hosted!.url, Uri.parse('hosted_url')); expect(dep.toString(), 'HostedDependency: any'); }); test('map w/ null hosted should error', () { _expectThrows( {'hosted': null}, r''' line 5, column 4: These keys had `null` values, which is not allowed: [hosted] ╷ 5 │ "hosted": null │ ^^^^^^^^ ╵''', ); }); test('map w/ null version is fine', () async { final dep = await _dependency({'version': null}); expect(dep.version, VersionConstraint.any); expect(dep.hosted, isNull); expect(dep.toString(), 'HostedDependency: any'); }); } void _sdkDependency() { test('without version', () async { final dep = await _dependency({'sdk': 'flutter'}); expect(dep.sdk, 'flutter'); expect(dep.version, VersionConstraint.any); expect(dep.toString(), 'SdkDependency: flutter'); }); test('with version', () async { final dep = await _dependency( {'sdk': 'flutter', 'version': '>=1.2.3 <2.0.0'}, ); expect(dep.sdk, 'flutter'); expect(dep.version.toString(), '>=1.2.3 <2.0.0'); expect(dep.toString(), 'SdkDependency: flutter'); }); test('null content', () { _expectThrowsContaining( {'sdk': null}, r"type 'Null' is not a subtype of type 'String'", ); }); test('number content', () { _expectThrowsContaining( {'sdk': 42}, r"type 'int' is not a subtype of type 'String'", ); }); } void _gitDependency() { test('string', () async { final dep = await _dependency({'git': 'url'}); expect(dep.url.toString(), 'url'); expect(dep.path, isNull); expect(dep.ref, isNull); expect(dep.toString(), 'GitDependency: url@url'); }); test('string with version key is ignored', () async { // Regression test for https://github.com/dart-lang/pubspec_parse/issues/13 final dep = await _dependency({'git': 'url', 'version': '^1.2.3'}); expect(dep.url.toString(), 'url'); expect(dep.path, isNull); expect(dep.ref, isNull); expect(dep.toString(), 'GitDependency: url@url'); }); test('string with user@ URL', () async { final skipTryParse = Platform.environment.containsKey('TRAVIS'); if (skipTryParse) { print('FYI: not validating git@ URI on travis due to failure'); } final dep = await _dependency( {'git': 'git@localhost:dep.git'}, skipTryPub: skipTryParse, ); expect(dep.url.toString(), 'ssh://git@localhost/dep.git'); expect(dep.path, isNull); expect(dep.ref, isNull); expect(dep.toString(), 'GitDependency: url@ssh://git@localhost/dep.git'); }); test('string with random extra key fails', () { _expectThrows( {'git': 'url', 'bob': '^1.2.3'}, r''' line 6, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted] ╷ 6 │ "bob": "^1.2.3" │ ^^^^^ ╵''', ); }); test('map', () async { final dep = await _dependency({ 'git': {'url': 'url', 'path': 'path', 'ref': 'ref'}, }); expect(dep.url.toString(), 'url'); expect(dep.path, 'path'); expect(dep.ref, 'ref'); expect(dep.toString(), 'GitDependency: url@url'); }); test('git - null content', () { _expectThrows( {'git': null}, r''' line 5, column 11: Unsupported value for "git". Must be a String or a Map. ╷ 5 │ "git": null │ ┌───────────^ 6 │ │ } │ └──^ ╵''', ); }); test('git - int content', () { _expectThrows( {'git': 42}, r''' line 5, column 11: Unsupported value for "git". Must be a String or a Map. ╷ 5 │ "git": 42 │ ┌───────────^ 6 │ │ } │ └──^ ╵''', ); }); test('git - empty map', () { _expectThrowsContaining( {'git': {}}, r"type 'Null' is not a subtype of type 'String'", ); }); test('git - null url', () { _expectThrowsContaining( { 'git': {'url': null}, }, r"type 'Null' is not a subtype of type 'String'", ); }); test('git - int url', () { _expectThrowsContaining( { 'git': {'url': 42}, }, r"type 'int' is not a subtype of type 'String'", ); }); } void _pathDependency() { test('valid', () async { final dep = await _dependency({'path': '../path'}); expect(dep.path, '../path'); expect(dep.toString(), 'PathDependency: path@../path'); }); test('valid with version key is ignored', () async { final dep = await _dependency( {'path': '../path', 'version': '^1.2.3'}, ); expect(dep.path, '../path'); expect(dep.toString(), 'PathDependency: path@../path'); }); test('valid with random extra key fails', () { _expectThrows( {'path': '../path', 'bob': '^1.2.3'}, r''' line 6, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted] ╷ 6 │ "bob": "^1.2.3" │ ^^^^^ ╵''', ); }); test('null content', () { _expectThrows( {'path': null}, r''' line 5, column 12: Unsupported value for "path". Must be a String. ╷ 5 │ "path": null │ ┌────────────^ 6 │ │ } │ └──^ ╵''', ); }); test('int content', () { _expectThrows( {'path': 42}, r''' line 5, column 12: Unsupported value for "path". Must be a String. ╷ 5 │ "path": 42 │ ┌────────────^ 6 │ │ } │ └──^ ╵''', ); }); } void _expectThrows(Object content, String expectedError) { expectParseThrows( { 'name': 'sample', 'dependencies': {'dep': content}, }, expectedError, ); } void _expectThrowsContaining(Object content, String errorText) { expectParseThrowsContaining( { 'name': 'sample', 'dependencies': {'dep': content}, }, errorText, ); } Future _dependency( Object? content, { bool skipTryPub = false, }) async { final value = await parse( { ...defaultPubspec, 'dependencies': {'dep': content}, }, skipTryPub: skipTryPub, ); expect(value.name, 'sample'); expect(value.dependencies, hasLength(1)); final entry = value.dependencies.entries.single; expect(entry.key, 'dep'); return entry.value as T; }