Skip to content

test: move expectThrow values to annotation on target elements #265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions json_serializable/test/analysis_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import 'dart:async';

import 'package:analyzer/dart/analysis/context_builder.dart';
import 'package:analyzer/dart/analysis/context_locator.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:source_gen/source_gen.dart';

Future<CompilationUnit> resolveCompilationUnit(String path) async {
Future<LibraryReader> resolveCompilationUnit(String path) async {
var contextLocator = new ContextLocator();
var roots = contextLocator.locateRoots(includedPaths: [path]);
if (roots.length != 1) {
Expand All @@ -18,5 +18,5 @@ Future<CompilationUnit> resolveCompilationUnit(String path) async {
var analysisContext =
new ContextBuilder().createContext(contextRoot: roots.single);
var resolveResult = await analysisContext.currentSession.getResolvedAst(path);
return resolveResult.unit;
return new LibraryReader(resolveResult.unit.element.library);
}
216 changes: 65 additions & 151 deletions json_serializable/test/json_serializable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

@TestOn('vm')

import 'package:analyzer/dart/ast/ast.dart';
import 'package:dart_style/dart_style.dart' as dart_style;
import 'package:json_serializable/json_serializable.dart';
import 'package:json_serializable/src/constants.dart';
import 'package:source_gen/source_gen.dart';
import 'package:test/test.dart';

import 'analysis_utils.dart';
import 'src/annotation.dart';
import 'test_file_utils.dart';

Matcher _throwsInvalidGenerationSourceError(messageMatcher, todoMatcher) =>
Expand All @@ -26,13 +26,13 @@ Matcher _throwsUnsupportedError(matcher) =>

final _formatter = new dart_style.DartFormatter();

CompilationUnit _compilationUnit;
LibraryReader _library;

void main() {
setUpAll(() async {
var path = testFilePath('test', 'src', 'json_serializable_test_input.dart');
_compilationUnit = await resolveCompilationUnit(path);
});
const _shouldThrowChecker = const TypeChecker.fromRuntime(ShouldThrow);

void main() async {
var path = testFilePath('test', 'src', 'json_serializable_test_input.dart');
_library = await resolveCompilationUnit(path);

group('without wrappers',
() => _registerTests(const JsonSerializableGenerator()));
Expand All @@ -41,8 +41,7 @@ void main() {
}

String _runForElementNamed(JsonSerializableGenerator generator, String name) {
var library = new LibraryReader(_compilationUnit.element.library);
var element = library.allElements.singleWhere((e) => e.name == name);
var element = _library.allElements.singleWhere((e) => e.name == name);
var annotation = generator.typeChecker.firstAnnotationOf(element);
var generated = generator
.generateForAnnotatedElement(
Expand All @@ -62,11 +61,68 @@ void _registerTests(JsonSerializableGenerator generator) {
_runForElementNamed(generator, name);

void expectThrows(String elementName, messageMatcher, [todoMatcher]) {
if (messageMatcher == null) {
var element =
_library.allElements.singleWhere((e) => e.name == elementName);

var constantValue = _shouldThrowChecker.firstAnnotationOfExact(element);
messageMatcher = constantValue.getField('errorMessage').toStringValue();
todoMatcher ??= constantValue.getField('todo').toStringValue();
}
todoMatcher ??= isEmpty;
expect(() => runForElementNamed(elementName),
_throwsInvalidGenerationSourceError(messageMatcher, todoMatcher));
}

group('fails when generating for', () {
var annotatedElements = _library
.annotatedWithExact(_shouldThrowChecker)
.toList()
..sort((a, b) => a.element.name.compareTo(b.element.name));

test('all expected members', () {
expect(annotatedElements.map((ae) => ae.element.name), [
'BadFromFuncReturnType',
'BadNoArgs',
'BadOneNamed',
'BadToFuncReturnType',
'BadTwoRequiredPositional',
'DefaultWithConstObject',
'DefaultWithFunction',
'DefaultWithNestedEnum',
'DefaultWithNonNullableClass',
'DefaultWithNonNullableField',
'DefaultWithSymbol',
'DefaultWithType',
'DupeKeys',
'IncludeIfNullDisallowNullClass',
'InvalidFromFunc2Args',
'InvalidFromFuncClassStatic',
'InvalidToFunc2Args',
'InvalidToFuncClassStatic',
'JsonValueWithBool',
'KeyDupesField',
'annotatedMethod',
'theAnswer',
]);
});

for (var annotatedElement in annotatedElements) {
var element = annotatedElement.element;
var constantValue = _shouldThrowChecker.firstAnnotationOfExact(element);
var testDescription =
constantValue.getField('testDescription').toStringValue();
var messageMatcher =
constantValue.getField('errorMessage').toStringValue();
var todoMatcher = constantValue.getField('todo').toStringValue();

test('$testDescription (${element.name})', () {
expectThrows(
annotatedElement.element.name, messageMatcher, todoMatcher);
});
}
});

group('explicit toJson', () {
test('nullable', () {
var output = _runForElementNamed(
Expand Down Expand Up @@ -158,20 +214,6 @@ Map<String, dynamic> _$TrivialNestedNonNullableToJson(
});
});

group('non-classes', () {
test('const field', () {
expectThrows('theAnswer', 'Generator cannot target `theAnswer`.',
'Remove the JsonSerializable annotation from `theAnswer`.');
});

test('method', () {
expectThrows(
'annotatedMethod',
'Generator cannot target `annotatedMethod`.',
'Remove the JsonSerializable annotation from `annotatedMethod`.');
});
});

group('unknown types', () {
String flavorMessage(String flavor) =>
'Could not generate `$flavor` code for `number` '
Expand Down Expand Up @@ -402,18 +444,6 @@ Map<String, dynamic> _$OrderToJson(Order instance) => <String, dynamic>{
'_privateField. It is assigned to a private field.'));
});
}

test('fails if name duplicates existing field', () {
expectThrows(
'KeyDupesField',
'More than one field has the JSON key `str`.',
'Check the `JsonKey` annotations on fields.');
});

test('fails if two names collide', () {
expectThrows('DupeKeys', 'More than one field has the JSON key `a`.',
'Check the `JsonKey` annotations on fields.');
});
});

group('includeIfNull', () {
Expand All @@ -433,62 +463,6 @@ Map<String, dynamic> _$OrderToJson(Order instance) => <String, dynamic>{
});

group('functions', () {
group('fromJsonFunction', () {
test('with bad fromJson return type', () {
expectThrows(
'BadFromFuncReturnType',
'Error with `@JsonKey` on `field`. The `fromJson` function `_toInt` '
'return type `int` is not compatible with field type `String`.');
});
test('with 2 arg fromJson function', () {
expectThrows(
'InvalidFromFunc2Args',
'Error with `@JsonKey` on `field`. The `fromJson` function '
'`_twoArgFunction` must have one positional paramater.');
});
test('with class static function', () {
expectThrows(
'InvalidFromFuncClassStatic',
'Error with `@JsonKey` on `field`. '
'The function provided for `fromJson` must be top-level. '
'Static class methods (`_staticFunc`) are not supported.');
});
test('BadNoArgs', () {
expectThrows('BadNoArgs',
'Error with `@JsonKey` on `field`. The `fromJson` function `_noArgs` must have one positional paramater.');
});
test('BadTwoRequiredPositional', () {
expectThrows('BadTwoRequiredPositional',
'Error with `@JsonKey` on `field`. The `fromJson` function `_twoArgs` must have one positional paramater.');
});
test('BadOneNamed', () {
expectThrows('BadOneNamed',
'Error with `@JsonKey` on `field`. The `fromJson` function `_oneNamed` must have one positional paramater.');
});
});

group('toJsonFunction', () {
test('with bad fromJson return type', () {
expectThrows(
'BadToFuncReturnType',
'Error with `@JsonKey` on `field`. The `toJson` function `_toInt` '
'argument type `bool` is not compatible with field type `String`.');
});
test('with 2 arg fromJson function', () {
expectThrows(
'InvalidToFunc2Args',
'Error with `@JsonKey` on `field`. The `toJson` function '
'`_twoArgFunction` must have one positional paramater.');
});
test('with class static function', () {
expectThrows(
'InvalidToFuncClassStatic',
'Error with `@JsonKey` on `field`. '
'The function provided for `toJson` must be top-level. '
'Static class methods (`_staticFunc`) are not supported.');
});
});

if (!generator.useWrappers) {
test('object', () {
var output = runForElementNamed('ObjectConvertMethods');
Expand Down Expand Up @@ -705,13 +679,6 @@ Map<String, dynamic> _$SubTypeToJson(SubType instance) {

if (!generator.useWrappers) {
group('enums annotated with JsonValue', () {
test('must be String, int, or null values', () {
expectThrows(
'JsonValueWithBool',
'The `JsonValue` annotation on `BadEnum.value` does not have a value '
'of type String, int, or null.');
});

test('can be interesting', () {
var output = runForElementNamed('JsonValueValid');

Expand All @@ -725,58 +692,5 @@ const _$GoodEnumEnumMap = const <GoodEnum, dynamic>{
};'''));
});
});

group('default values fail with', () {
test('symbols', () {
expectThrows(
'DefaultWithSymbol',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Symbol`, it must be a literal.');
});
test('functions', () {
expectThrows(
'DefaultWithFunction',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Function`, it must be a literal.');
});
test('type', () {
expectThrows(
'DefaultWithType',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Type`, it must be a literal.');
});
test('const object', () {
expectThrows(
'DefaultWithConstObject',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Duration`, it must be a literal.');
});
test('enum value', () {
expectThrows(
'DefaultWithNestedEnum',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `List > Enum`, it must be a literal.');
});
test('non-nullable field', () {
expectThrows(
'DefaultWithNonNullableField',
'Error with `@JsonKey` on `field`. '
'Cannot use `defaultValue` on a field with `nullable` false.');
});
test('non-nullable class', () {
expectThrows(
'DefaultWithNonNullableClass',
'Error with `@JsonKey` on `field`. '
'Cannot use `defaultValue` on a field with `nullable` false.');
});
});

test('`disallowNullvalue` and `includeIfNull` both `true`', () {
expectThrows(
'IncludeIfNullDisallowNullClass',
'Error with `@JsonKey` on `field`. '
'Cannot set both `disallowNullvalue` and `includeIfNull` to `true`. '
'This leads to incompatible `toJson` and `fromJson` behavior.');
});
}
}
10 changes: 10 additions & 0 deletions json_serializable/test/src/annotation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// 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.

class ShouldThrow {
final String testDescription;
final String errorMessage;
final String todo;
const ShouldThrow(this.testDescription, this.errorMessage, [this.todo]);
}
28 changes: 28 additions & 0 deletions json_serializable/test/src/default_value_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

part of 'json_serializable_test_input.dart';

@ShouldThrow(
'default values fail with symbol',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Symbol`, it must be a literal.')
@JsonSerializable()
class DefaultWithSymbol {
@JsonKey(defaultValue: #symbol)
Expand All @@ -14,6 +18,10 @@ class DefaultWithSymbol {

int _function() => 42;

@ShouldThrow(
'default values fail with function',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Function`, it must be a literal.')
@JsonSerializable()
class DefaultWithFunction {
@JsonKey(defaultValue: _function)
Expand All @@ -22,6 +30,10 @@ class DefaultWithFunction {
DefaultWithFunction();
}

@ShouldThrow(
'default values fail with Type',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Type`, it must be a literal.')
@JsonSerializable()
class DefaultWithType {
@JsonKey(defaultValue: Object)
Expand All @@ -30,6 +42,10 @@ class DefaultWithType {
DefaultWithType();
}

@ShouldThrow(
'default values fail with const object',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `Duration`, it must be a literal.')
@JsonSerializable()
class DefaultWithConstObject {
@JsonKey(defaultValue: const Duration())
Expand All @@ -40,6 +56,10 @@ class DefaultWithConstObject {

enum Enum { value }

@ShouldThrow(
'default values fail with nested enum',
'Error with `@JsonKey` on `field`. '
'`defaultValue` is `List > Enum`, it must be a literal.')
@JsonSerializable()
class DefaultWithNestedEnum {
@JsonKey(defaultValue: [Enum.value])
Expand All @@ -48,6 +68,10 @@ class DefaultWithNestedEnum {
DefaultWithNestedEnum();
}

@ShouldThrow(
'default values fail with non-nullable field',
'Error with `@JsonKey` on `field`. '
'Cannot use `defaultValue` on a field with `nullable` false.')
@JsonSerializable()
class DefaultWithNonNullableField {
@JsonKey(defaultValue: 42, nullable: false)
Expand All @@ -56,6 +80,10 @@ class DefaultWithNonNullableField {
DefaultWithNonNullableField();
}

@ShouldThrow(
'default values fail with non-nullable class',
'Error with `@JsonKey` on `field`. '
'Cannot use `defaultValue` on a field with `nullable` false.')
@JsonSerializable(nullable: false)
class DefaultWithNonNullableClass {
@JsonKey(defaultValue: 42)
Expand Down
Loading