blob: 197d317b4dac98bcf01b8d357944038efbf599d8 [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.
@TestOn('vm && !windows')
@Timeout.factor(2)
library;
import 'dart:io';
import 'package:analyzer/dart/element/element2.dart';
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'builder_test_base.dart';
void main() {
group('builds a renderer class', () {
late final LibraryElement2 renderersLibrary;
late final String generatedContent;
// Builders are fairly expensive (about 4 seconds per `testBuilder` call),
// so this [setUpAll] saves significant time over [setUp].
setUpAll(() async {
await testMustachioBuilder('''
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 resolveGeneratedLibrary2(runtimeRenderersPath);
generatedContent = await File(runtimeRenderersPath).readAsString();
});
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.getClass2('_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.getClass2('_Renderer_Mix'), isNotNull);
});
test('for a type found in a getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Bar'), isNotNull);
expect(renderersLibrary.getClass2('_Renderer_Bar'), isNotNull);
});
test('for a generic, bounded type found in a getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Baz'), isNotNull);
expect(renderersLibrary.getClass2('_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,
),
'''));
});
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, non-nullable 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) => false,
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('''
class Foo {
String s1 = 'hello';
}
class Bar {}
class Baz {}
''', libraryFrontMatter: '''
@Renderer(#renderFoo, Context<Foo>(), 'foo')
@Renderer(#renderBar, Context<Bar>(), 'bar')
library foo;
import 'annotations.dart';
''');
var renderersLibrary = await resolveGeneratedLibrary2(runtimeRenderersPath);
expect(renderersLibrary.getTopLevelFunction('renderFoo'), isNotNull);
expect(renderersLibrary.getTopLevelFunction('renderBar'), isNotNull);
expect(renderersLibrary.getClass2('_Renderer_Foo'), isNotNull);
expect(renderersLibrary.getClass2('_Renderer_Bar'), isNotNull);
});
group('builds a renderer class for a generic type', () {
late final String generatedContent;
// Builders are fairly expensive (about 4 seconds per `testBuilder` call),
// so this [setUpAll] saves significant time over [setUp].
setUpAll(() async {
await testMustachioBuilder('''
class FooBase<T> {}
class Foo<T> extends FooBase<T> {
String s1 = 'hello';
}
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 'annotations.dart';
''');
generatedContent = await File(runtimeRenderersPath).readAsString();
});
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>(
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('''
class Foo<T extends num> {
String s1 = 'hello';
}
class Bar {}
class Baz {}
''');
var renderersLibrary = await resolveGeneratedLibrary2(runtimeRenderersPath);
var fooRenderFunction = renderersLibrary.getTopLevelFunction('renderFoo')!;
expect(fooRenderFunction.typeParameters2, hasLength(1));
var fBound = fooRenderFunction.typeParameters2.single.bound!;
expect(fBound.getDisplayString(), equals('num'));
var fooRendererClass = renderersLibrary.getClass2('_Renderer_Foo')!;
expect(fooRendererClass.typeParameters2, hasLength(1));
var cBound = fooRenderFunction.typeParameters2.single.bound!;
expect(cBound.getDisplayString(), equals('num'));
});
group('does not generate a renderer', () {
late final LibraryElement2 renderersLibrary;
setUpAll(() async {
await testMustachioBuilder('''
abstract class Foo<T> {
static Static get static1 => Bar();
Private get _private1 => Bar();
void set setter1(Setter s);
Method method1(Method m);
String s1 = 'hello';
}
class Bar {}
class Baz {}
class Static {}
class Private {}
class Setter {}
class Method {}
''');
renderersLibrary = await resolveGeneratedLibrary2(runtimeRenderersPath);
});
test('found in a static getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Static'), isNull);
expect(renderersLibrary.getClass2('_Renderer_Static'), isNull);
});
test('found in a private getter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Private'), isNull);
expect(renderersLibrary.getClass2('_Renderer_Private'), isNull);
});
test('found in a setter', () {
expect(renderersLibrary.getTopLevelFunction('_render_Setter'), isNull);
expect(renderersLibrary.getClass2('_Renderer_Setter'), isNull);
});
test('found in a method', () {
expect(renderersLibrary.getTopLevelFunction('_render_Method'), isNull);
expect(renderersLibrary.getClass2('_Renderer_Method'), isNull);
});
test('for types not @visibleToMustache', () {
expect(renderersLibrary.getTopLevelFunction('_render_String'), isNull);
expect(renderersLibrary.getClass2('_Renderer_String'), isNull);
});
});
}
String get runtimeRenderersPath =>
'${d.sandbox}/foo_package/lib/foo.runtime_renderers.dart';