blob: 65dcf65c96eb670fa143cb9f49384cbebf16efb9 [file] [log] [blame]
// Copyright (c) 2021, 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.
@Timeout.factor(2)
import 'dart:convert';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:build_test/build_test.dart';
import 'package:test/test.dart';
import 'builder_test_base.dart';
void main() {
InMemoryAssetWriter writer;
Future<LibraryElement> resolveGeneratedLibrary() async {
var rendererAsset = AssetId('foo', 'lib/foo.runtime_renderers.dart');
var writtenStrings = writer.assets
.map((id, content) => MapEntry(id.toString(), utf8.decode(content)));
return await resolveSources(writtenStrings,
(Resolver resolver) => resolver.libraryFor(rendererAsset));
}
setUp(() {
writer = InMemoryAssetWriter();
});
group('builds a renderer class', () {
LibraryElement renderersLibrary;
String generatedContent;
// Builders are fairly expensive (about 4 seconds per `testBuilder` call),
// so this [setUpAll] saves significant time over [setUp].
setUpAll(() async {
writer = InMemoryAssetWriter();
await testMustachioBuilder(writer, '''
abstract class FooBase2<T> {
T get generic;
}
abstract class FooBase<T extends Baz> extends FooBase2<T> {
Bar get bar;
}
mixin Mix<E> on FooBase<Baz> {
String get field => 'Mix.field';
}
abstract class Foo extends FooBase with Mix<int> {
String s1 = "s1";
bool b1 = false;
List<int> l1 = [1, 2, 3];
}
class Bar {}
class Baz {}
''');
renderersLibrary = await resolveGeneratedLibrary();
var rendererAsset = AssetId('foo', 'lib/foo.runtime_renderers.dart');
generatedContent = utf8.decode(writer.assets[rendererAsset]);
});
test('for a class which implicitly extends Object', () {
// The renderer class for Foo
expect(
generatedContent,
contains(
'class _Renderer_FooBase<T extends Baz> extends RendererBase<FooBase<T>>'));
});
test('for Object', () {
// The renderer class for Object
expect(generatedContent,
contains('class _Renderer_Object extends RendererBase<Object> {'));
});
test('for a class which is extended by a rendered class', () {
// No render function is necessary.
expect(renderersLibrary.getTopLevelFunction('_render_FooBase'), isNull);
expect(renderersLibrary.getType('_Renderer_FooBase'), isNotNull);
});
test('for a class which is mixed into a rendered class', () {
// No render function is necessary.
expect(renderersLibrary.getTopLevelFunction('_render_Mix'), isNull);
expect(renderersLibrary.getType('_Renderer_Mix'), isNotNull);
});
test('for a type found in a getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Bar'), isNotNull);
expect(renderersLibrary.getType('_Renderer_Bar'), isNotNull);
});
test('for a generic, bounded type found in a getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Baz'), isNotNull);
expect(renderersLibrary.getType('_Renderer_Baz'), isNotNull);
});
test('with a property map', () {
expect(
generatedContent,
contains(
'static Map<String, Property<CT_>> propertyMap<CT_ extends Foo>() =>'));
});
test('with a property map which references the superclass', () {
expect(generatedContent,
contains('..._Renderer_FooBase.propertyMap<Baz, CT_>(),'));
});
test('with a property map which references a mixed in class', () {
expect(generatedContent,
contains('..._Renderer_Mix.propertyMap<int, CT_>(),'));
});
test('with a property map with a bool property', () {
expect(generatedContent, contains('''
'b1': Property(
getValue: (CT_ c) => c.b1,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'bool'),
getBool: (CT_ c) => c.b1 == true,
),
'''));
});
test('with a property map with an Iterable property', () {
expect(generatedContent, contains('''
'l1': Property(
getValue: (CT_ c) => c.l1,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'List<int>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.l1.map((e) => renderSimple(
e, ast, r.template, sink,
parent: r, getters: _invisibleGetters['int']));
},
),
'''));
});
test('with a property map with a non-bool, non-Iterable property', () {
expect(generatedContent, contains('''
's1': Property(
getValue: (CT_ c) => c.s1,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'String'),
isNullValue: (CT_ c) => c.s1 == null,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
renderSimple(c.s1, ast, r.template, sink,
parent: r, getters: _invisibleGetters['String']);
},
),
'''));
});
});
test('builds renderers from multiple annotations', () async {
await testMustachioBuilder(writer, '''
class Foo {}
class Bar {}
class Baz {}
''', libraryFrontMatter: '''
@Renderer(#renderFoo, Context<Foo>(), 'foo')
@Renderer(#renderBar, Context<Bar>(), 'bar')
library foo;
import 'package:mustachio/annotations.dart';
''');
var renderersLibrary = await resolveGeneratedLibrary();
expect(renderersLibrary.getTopLevelFunction('renderFoo'), isNotNull);
expect(renderersLibrary.getTopLevelFunction('renderBar'), isNotNull);
expect(renderersLibrary.getType('_Renderer_Foo'), isNotNull);
expect(renderersLibrary.getType('_Renderer_Bar'), isNotNull);
});
group('builds a renderer class for a generic type', () {
String generatedContent;
// Builders are fairly expensive (about 4 seconds per `testBuilder` call),
// so this [setUpAll] saves significant time over [setUp].
setUpAll(() async {
writer = InMemoryAssetWriter();
await testMustachioBuilder(writer, '''
class FooBase<T> {}
class Foo<T> extends FooBase<T> {}
class BarBase<T> {}
class Bar<T> extends BarBase<int> {}
class Baz {}
''', libraryFrontMatter: '''
@Renderer(#renderFoo, Context<Foo>(), 'foo')
@Renderer(#renderBar, Context<Bar>(), 'bar')
library foo;
import 'package:mustachio/annotations.dart';
''');
var rendererAsset = AssetId('foo', 'lib/foo.runtime_renderers.dart');
generatedContent = utf8.decode(writer.assets[rendererAsset]);
});
test('with a corresponding public API function', () async {
expect(generatedContent,
contains('String renderFoo<T>(Foo<T> context, Template template)'));
});
test('with a corresponding render function', () async {
expect(
generatedContent,
contains('void _render_Foo<T>(\n'
' Foo<T> context, List<MustachioNode> ast, Template template, StringSink sink'));
});
test('with a generic supertype type argument', () async {
expect(generatedContent,
contains('class _Renderer_Foo<T> extends RendererBase<Foo<T>>'));
});
test(
'with a property map which references the superclass with a type '
'variable', () {
expect(generatedContent,
contains('..._Renderer_FooBase.propertyMap<T, CT_>(),'));
});
test(
'with a property map which references the superclass with an interface '
'type', () {
expect(generatedContent,
contains('..._Renderer_BarBase.propertyMap<int, CT_>(),'));
});
});
test('builds a renderer for a generic, bounded type', () async {
await testMustachioBuilder(writer, '''
class Foo<T extends num> {}
class Bar {}
class Baz {}
''');
var renderersLibrary = await resolveGeneratedLibrary();
var fooRenderFunction = renderersLibrary.getTopLevelFunction('renderFoo');
expect(fooRenderFunction.typeParameters, hasLength(1));
var fBound = fooRenderFunction.typeParameters.single.bound;
expect(fBound.getDisplayString(withNullability: false), equals('num'));
var fooRendererClass = renderersLibrary.getType('_Renderer_Foo');
expect(fooRendererClass.typeParameters, hasLength(1));
var cBound = fooRenderFunction.typeParameters.single.bound;
expect(cBound.getDisplayString(withNullability: false), equals('num'));
});
group('does not generate a renderer', () {
LibraryElement renderersLibrary;
setUpAll(() async {
writer = InMemoryAssetWriter();
await testMustachioBuilder(writer, '''
abstract class Foo<T> {
static Static get static1 => Bar();
Private get _private1 => Bar();
void set setter1(Setter s);
Method method1(Method m);
}
class Bar {}
class Baz {}
class Static {}
class Private {}
class Setter {}
class Method {}
''');
renderersLibrary = await resolveGeneratedLibrary();
});
test('found in a static getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Static'), isNull);
expect(renderersLibrary.getType('_Renderer_Static'), isNull);
});
test('found in a private getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Private'), isNull);
expect(renderersLibrary.getType('_Renderer_Private'), isNull);
});
test('found in a setter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Setter'), isNull);
expect(renderersLibrary.getType('_Renderer_Setter'), isNull);
});
test('found in a method', () {
expect(renderersLibrary.getTopLevelFunction('_render_Method'), isNull);
expect(renderersLibrary.getType('_Renderer_Method'), isNull);
});
test('for types not @visibleToMustache', () {
expect(renderersLibrary.getTopLevelFunction('_render_String'), isNull);
expect(renderersLibrary.getType('_Renderer_String'), isNull);
});
});
}