// 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. // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: lines_longer_than_80_chars import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; void main() { test('minimal set values', () async { final value = await parse(defaultPubspec); expect(value.name, 'sample'); expect(value.version, isNull); expect(value.publishTo, isNull); expect(value.description, isNull); expect(value.homepage, isNull); expect(value.author, isNull); expect(value.authors, isEmpty); expect( value.environment, {'sdk': VersionConstraint.parse('>=2.12.0 <3.0.0')}, ); expect(value.documentation, isNull); expect(value.dependencies, isEmpty); expect(value.devDependencies, isEmpty); expect(value.dependencyOverrides, isEmpty); expect(value.flutter, isNull); expect(value.repository, isNull); expect(value.issueTracker, isNull); expect(value.screenshots, isEmpty); expect(value.workspace, isNull); expect(value.resolution, isNull); expect(value.executables, isEmpty); }); test('all fields set', () async { final version = Version.parse('1.2.3'); final sdkConstraint = VersionConstraint.parse('>=3.6.0 <4.0.0'); final value = await parse( { 'name': 'sample', 'version': version.toString(), 'publish_to': 'none', 'author': 'name@example.com', 'environment': {'sdk': sdkConstraint.toString()}, 'description': 'description', 'homepage': 'homepage', 'documentation': 'documentation', 'repository': 'https://github.com/example/repo', 'issue_tracker': 'https://github.com/example/repo/issues', 'funding': [ 'https://patreon.com/example', ], 'topics': ['widget', 'button'], 'ignored_advisories': ['111', '222'], 'screenshots': [ {'description': 'my screenshot', 'path': 'path/to/screenshot'}, ], 'workspace': [ 'pkg1', 'pkg2', ], 'resolution': 'workspace', 'executables': { 'my_script': 'bin/my_script.dart', 'my_script2': 'bin/my_script2.dart', }, }, skipTryPub: true, ); expect(value.name, 'sample'); expect(value.version, version); expect(value.publishTo, 'none'); expect(value.description, 'description'); expect(value.homepage, 'homepage'); expect(value.author, 'name@example.com'); expect(value.authors, ['name@example.com']); expect(value.environment, hasLength(1)); expect(value.environment, containsPair('sdk', sdkConstraint)); expect(value.documentation, 'documentation'); expect(value.dependencies, isEmpty); expect(value.devDependencies, isEmpty); expect(value.dependencyOverrides, isEmpty); expect(value.repository, Uri.parse('https://github.com/example/repo')); expect( value.issueTracker, Uri.parse('https://github.com/example/repo/issues'), ); expect(value.funding, hasLength(1)); expect(value.funding!.single.toString(), 'https://patreon.com/example'); expect(value.topics, hasLength(2)); expect(value.topics!.first, 'widget'); expect(value.topics!.last, 'button'); expect(value.ignoredAdvisories, hasLength(2)); expect(value.ignoredAdvisories!.first, '111'); expect(value.ignoredAdvisories!.last, '222'); expect(value.screenshots, hasLength(1)); expect(value.screenshots!.first.description, 'my screenshot'); expect(value.screenshots!.first.path, 'path/to/screenshot'); expect(value.executables, hasLength(2)); expect(value.executables.keys, contains('my_script')); expect(value.executables.keys, contains('my_script2')); expect(value.executables['my_script'], 'bin/my_script.dart'); expect(value.executables['my_script2'], 'bin/my_script2.dart'); expect(value.workspace, hasLength(2)); expect(value.workspace!.first, 'pkg1'); expect(value.workspace!.last, 'pkg2'); expect(value.resolution, 'workspace'); }); test('environment values can be null', () async { final value = await parse( { 'name': 'sample', 'environment': { 'sdk': '>=2.12.0 <3.0.0', 'bob': null, }, }, skipTryPub: true, ); expect(value.name, 'sample'); expect(value.environment, hasLength(2)); expect(value.environment, containsPair('bob', isNull)); }); group('publish_to', () { for (var entry in { 42: "Unsupported value for \"publish_to\". type 'int' is not a subtype of type 'String?'", '##not a uri!': r''' line 3, column 16: Unsupported value for "publish_to". Must be an http or https URL. ╷ 3 │ "publish_to": "##not a uri!" │ ^^^^^^^^^^^^^^ ╵''', '/cool/beans': r''' line 3, column 16: Unsupported value for "publish_to". Must be an http or https URL. ╷ 3 │ "publish_to": "/cool/beans" │ ^^^^^^^^^^^^^ ╵''', 'file:///Users/kevmoo/': r''' line 3, column 16: Unsupported value for "publish_to". Must be an http or https URL. ╷ 3 │ "publish_to": "file:///Users/kevmoo/" │ ^^^^^^^^^^^^^^^^^^^^^^^ ╵''', }.entries) { test('cannot be `${entry.key}`', () { expectParseThrowsContaining( {'name': 'sample', 'publish_to': entry.key}, entry.value, skipTryPub: true, ); }); } for (var entry in { null: null, 'http': 'http://example.com', 'https': 'https://example.com', 'none': 'none', }.entries) { test('can be ${entry.key}', () async { final value = await parse({ ...defaultPubspec, 'publish_to': entry.value, }); expect(value.publishTo, entry.value); }); } }); group('author, authors', () { test('one author', () async { final value = await parse({ ...defaultPubspec, 'author': 'name@example.com', }); expect(value.author, 'name@example.com'); expect(value.authors, ['name@example.com']); }); test('one author, via authors', () async { final value = await parse({ ...defaultPubspec, 'authors': ['name@example.com'], }); expect(value.author, 'name@example.com'); expect(value.authors, ['name@example.com']); }); test('many authors', () async { final value = await parse({ ...defaultPubspec, 'authors': ['name@example.com', 'name2@example.com'], }); expect(value.author, isNull); expect(value.authors, ['name@example.com', 'name2@example.com']); }); test('author and authors', () async { final value = await parse({ ...defaultPubspec, 'author': 'name@example.com', 'authors': ['name2@example.com'], }); expect(value.author, isNull); expect(value.authors, ['name@example.com', 'name2@example.com']); }); test('duplicate author values', () async { final value = await parse({ ...defaultPubspec, 'author': 'name@example.com', 'authors': ['name@example.com', 'name@example.com'], }); expect(value.author, 'name@example.com'); expect(value.authors, ['name@example.com']); }); test('flutter', () async { final value = await parse({ ...defaultPubspec, 'flutter': {'key': 'value'}, }); expect(value.flutter, {'key': 'value'}); }); }); group('executables', () { test('one executable', () async { final value = await parse({ ...defaultPubspec, 'executables': {'my_script': 'bin/my_script.dart'}, }); expect(value.executables, hasLength(1)); expect(value.executables.keys, contains('my_script')); expect(value.executables['my_script'], 'bin/my_script.dart'); }); test('many executables', () async { final value = await parse({ ...defaultPubspec, 'executables': { 'my_script': 'bin/my_script.dart', 'my_script2': 'bin/my_script2.dart', }, }); expect(value.executables, hasLength(2)); expect(value.executables.keys, contains('my_script')); expect(value.executables.keys, contains('my_script2')); expect(value.executables['my_script'], 'bin/my_script.dart'); expect(value.executables['my_script2'], 'bin/my_script2.dart'); }); test('invalid value', () async { expectParseThrowsContaining( { ...defaultPubspec, 'executables': { 'script': 32, }, }, 'Unsupported value for "script". `32` is not a String.', skipTryPub: true, ); }); test('invalid executable - lenient', () async { final value = await parse( { ...defaultPubspec, 'executables': 'Invalid value', }, lenient: true, ); expect(value.name, 'sample'); expect(value.executables, isEmpty); }); }); group('invalid', () { test('null', () { expectParseThrows( null, r''' line 1, column 1: Not a map ╷ 1 │ null │ ^^^^ ╵''', ); }); test('empty string', () { expectParseThrows( '', r''' line 1, column 1: Not a map ╷ 1 │ "" │ ^^ ╵''', ); }); test('array', () { expectParseThrows( [], r''' line 1, column 1: Not a map ╷ 1 │ [] │ ^^ ╵''', ); }); test('missing name', () { expectParseThrowsContaining( {}, "Missing key \"name\". type 'Null' is not a subtype of type 'String'", ); }); test('null name value', () { expectParseThrowsContaining( {'name': null}, "Unsupported value for \"name\". type 'Null' is not a subtype of type 'String'", ); }); test('empty name value', () { expectParseThrows( {'name': ''}, r''' line 2, column 10: Unsupported value for "name". "name" cannot be empty. ╷ 2 │ "name": "" │ ^^ ╵''', ); }); test('"dart" is an invalid environment key', () { expectParseThrows( { 'name': 'sample', 'environment': {'dart': 'cool'}, }, r''' line 4, column 3: Use "sdk" to for Dart SDK constraints. ╷ 4 │ "dart": "cool" │ ^^^^^^ ╵''', ); }); test('environment values cannot be int', () { expectParseThrows( { 'name': 'sample', 'environment': {'sdk': 42}, }, r''' line 4, column 10: Unsupported value for "sdk". `42` is not a String. ╷ 4 │ "sdk": 42 │ ┌──────────^ 5 │ │ } │ └─^ ╵''', ); }); test('version', () { expectParseThrows( {'name': 'sample', 'version': 'invalid'}, r''' line 3, column 13: Unsupported value for "version". Could not parse "invalid". ╷ 3 │ "version": "invalid" │ ^^^^^^^^^ ╵''', ); }); test('invalid environment value', () { expectParseThrows( { 'name': 'sample', 'environment': {'sdk': 'silly'}, }, r''' line 4, column 10: Unsupported value for "sdk". Could not parse version "silly". Unknown text at "silly". ╷ 4 │ "sdk": "silly" │ ^^^^^^^ ╵''', ); }); test('bad repository url', () { expectParseThrowsContaining( { ...defaultPubspec, 'repository': {'x': 'y'}, }, "Unsupported value for \"repository\". type 'YamlMap' is not a subtype of type 'String'", skipTryPub: true, ); }); test('bad issue_tracker url', () { expectParseThrowsContaining( { 'name': 'sample', 'issue_tracker': {'x': 'y'}, }, "Unsupported value for \"issue_tracker\". type 'YamlMap' is not a subtype of type 'String'", skipTryPub: true, ); }); }); group('funding', () { test('not a list', () { expectParseThrowsContaining( { ...defaultPubspec, 'funding': 1, }, "Unsupported value for \"funding\". type 'int' is not a subtype of type 'List?'", skipTryPub: true, ); }); test('not an uri', () { expectParseThrowsContaining( { ...defaultPubspec, 'funding': [1], }, "Unsupported value for \"funding\". type 'int' is not a subtype of type 'String'", skipTryPub: true, ); }); test('not an uri', () { expectParseThrows( { ...defaultPubspec, 'funding': ['ht tps://example.com/'], }, r''' line 6, column 13: Unsupported value for "funding". Illegal scheme character at offset 2. ╷ 6 │ "funding": [ │ ┌─────────────^ 7 │ │ "ht tps://example.com/" 8 │ └ ] ╵''', skipTryPub: true, ); }); }); group('topics', () { test('not a list', () { expectParseThrowsContaining( { ...defaultPubspec, 'topics': 1, }, "Unsupported value for \"topics\". type 'int' is not a subtype of type 'List?'", skipTryPub: true, ); }); test('not a string', () { expectParseThrowsContaining( { ...defaultPubspec, 'topics': [1], }, "Unsupported value for \"topics\". type 'int' is not a subtype of type 'String'", skipTryPub: true, ); }); test('invalid data - lenient', () async { final value = await parse( { ...defaultPubspec, 'topics': [1], }, skipTryPub: true, lenient: true, ); expect(value.name, 'sample'); expect(value.topics, isNull); }); }); group('ignored_advisories', () { test('not a list', () { expectParseThrowsContaining( { ...defaultPubspec, 'ignored_advisories': 1, }, "Unsupported value for \"ignored_advisories\". type 'int' is not a subtype of type 'List?'", skipTryPub: true, ); }); test('not a string', () { expectParseThrowsContaining( { ...defaultPubspec, 'ignored_advisories': [1], }, "Unsupported value for \"ignored_advisories\". type 'int' is not a subtype of type 'String'", skipTryPub: true, ); }); test('invalid data - lenient', () async { final value = await parse( { ...defaultPubspec, 'ignored_advisories': [1], }, skipTryPub: true, lenient: true, ); expect(value.name, 'sample'); expect(value.ignoredAdvisories, isNull); }); }); group('screenshots', () { test('one screenshot', () async { final value = await parse({ ...defaultPubspec, 'screenshots': [ {'description': 'my screenshot', 'path': 'path/to/screenshot'}, ], }); expect(value.screenshots, hasLength(1)); expect(value.screenshots!.first.description, 'my screenshot'); expect(value.screenshots!.first.path, 'path/to/screenshot'); }); test('many screenshots', () async { final value = await parse({ ...defaultPubspec, 'screenshots': [ {'description': 'my screenshot', 'path': 'path/to/screenshot'}, { 'description': 'my second screenshot', 'path': 'path/to/screenshot2', }, ], }); expect(value.screenshots, hasLength(2)); expect(value.screenshots!.first.description, 'my screenshot'); expect(value.screenshots!.first.path, 'path/to/screenshot'); expect(value.screenshots!.last.description, 'my second screenshot'); expect(value.screenshots!.last.path, 'path/to/screenshot2'); }); test('one screenshot plus invalid entries', () async { final value = await parse({ ...defaultPubspec, 'screenshots': [ 42, { 'description': 'my screenshot', 'path': 'path/to/screenshot', 'extraKey': 'not important', }, 'not a screenshot', ], }); expect(value.screenshots, hasLength(1)); expect(value.screenshots!.first.description, 'my screenshot'); expect(value.screenshots!.first.path, 'path/to/screenshot'); }); test('invalid entries', () async { final value = await parse({ ...defaultPubspec, 'screenshots': [ 42, 'not a screenshot', ], }); expect(value.screenshots, isEmpty); }); test('missing key `dessription', () { expectParseThrows( { ...defaultPubspec, 'screenshots': [ {'path': 'my/path'}, ], }, r''' line 7, column 3: Missing key "description". Missing required key `description` ╷ 7 │ ┌ { 8 │ │ "path": "my/path" 9 │ └ } ╵''', skipTryPub: true, ); }); test('missing key `path`', () { expectParseThrows( { ...defaultPubspec, 'screenshots': [ {'description': 'my screenshot'}, ], }, r''' line 7, column 3: Missing key "path". Missing required key `path` ╷ 7 │ ┌ { 8 │ │ "description": "my screenshot" 9 │ └ } ╵''', skipTryPub: true, ); }); test('Value of description not a String`', () { expectParseThrows( { ...defaultPubspec, 'screenshots': [ {'description': 42}, ], }, r''' line 8, column 19: Unsupported value for "description". `42` is not a String ╷ 8 │ "description": 42 │ ┌───────────────────^ 9 │ │ } │ └──^ ╵''', skipTryPub: true, ); }); test('Value of path not a String`', () { expectParseThrows( { ...defaultPubspec, 'screenshots': [ { 'description': '', 'path': 42, }, ], }, r''' line 9, column 12: Unsupported value for "path". `42` is not a String ╷ 9 │ "path": 42 │ ┌────────────^ 10 │ │ } │ └──^ ╵''', skipTryPub: true, ); }); test('invalid screenshot - lenient', () async { final value = await parse( { ...defaultPubspec, 'screenshots': 'Invalid value', }, lenient: true, ); expect(value.name, 'sample'); expect(value.screenshots, isEmpty); }); }); group('lenient', () { test('null', () { expectParseThrows( null, r''' line 1, column 1: Not a map ╷ 1 │ null │ ^^^^ ╵''', lenient: true, ); }); test('empty string', () { expectParseThrows( '', r''' line 1, column 1: Not a map ╷ 1 │ "" │ ^^ ╵''', lenient: true, ); }); test('name cannot be empty', () { expectParseThrowsContaining( {}, "Missing key \"name\". type 'Null' is not a subtype of type 'String'", lenient: true, ); }); test('bad repository url', () async { final value = await parse( { ...defaultPubspec, 'repository': {'x': 'y'}, }, lenient: true, ); expect(value.name, 'sample'); expect(value.repository, isNull); }); test('bad issue_tracker url', () async { final value = await parse( { ...defaultPubspec, 'issue_tracker': {'x': 'y'}, }, lenient: true, ); expect(value.name, 'sample'); expect(value.issueTracker, isNull); }); test('multiple bad values', () async { final value = await parse( { ...defaultPubspec, 'repository': {'x': 'y'}, 'issue_tracker': {'x': 'y'}, }, lenient: true, ); expect(value.name, 'sample'); expect(value.repository, isNull); expect(value.issueTracker, isNull); }); test('deep error throws with lenient', () { expect( () => parse( { 'name': 'sample', 'dependencies': { 'foo': { 'git': {'url': 1}, }, }, 'issue_tracker': {'x': 'y'}, }, skipTryPub: true, lenient: true, ), throwsException, ); }); }); group('workspaces', () { test('workspace key must be a list', () { expectParseThrowsContaining( { ...defaultPubspec, 'workspace': 42, }, 'Unsupported value for "workspace". type \'int\' is not a subtype of type \'List?\' in type cast', skipTryPub: true, ); }); test('workspace key must be a list of strings', () { expectParseThrowsContaining( { ...defaultPubspec, 'workspace': [42], }, 'Unsupported value for "workspace". type \'int\' is not a subtype of type \'String\' in type cast', skipTryPub: true, ); }); test('resolution key must be a string', () { expectParseThrowsContaining( { 'name': 'sample', 'environment': {'sdk': '^3.6.0'}, 'resolution': 42, }, 'Unsupported value for "resolution". type \'int\' is not a subtype of type \'String?\' in type cast', skipTryPub: true, ); }); }); }