Skip to content

Improve mustachio builder tests; make assert on generated elements #2427

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 2 commits into from
Nov 16, 2020
Merged
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
230 changes: 103 additions & 127 deletions test/mustachio/builder_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
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';
Expand All @@ -22,6 +24,12 @@ class Context<T> {
'''
};

const _libraryFrontMatter = '''
@Renderer(#renderFoo, Context<Foo>(), 'foo.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';
''';

TypeMatcher<List<int>> _containsAllOf(Object a,
[Object b, Object c, Object d]) {
if (d != null) {
Expand All @@ -35,24 +43,44 @@ TypeMatcher<List<int>> _containsAllOf(Object a,
}
}

TypeMatcher<List<int>> _containsNoneOf(Object a, [Object b]) {
return decodedMatches(b != null
? allOf(isNot(contains(a)), isNot(contains(b)))
: allOf(isNot(contains(a))));
}

void main() {
test('builds a renderer for a class which extends Object', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
@Renderer(#renderFoo, Context<Foo>(), 'foo.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';
InMemoryAssetWriter writer;

Future<LibraryElement> resolveGeneratedLibrary(
InMemoryAssetWriter writer) async {
var rendererAsset = AssetId.parse('foo|lib/foo.renderers.dart');
var writtenStrings = writer.assets
.map((id, content) => MapEntry(id.toString(), utf8.decode(content)));
return await resolveSources(writtenStrings,
(Resolver resolver) => resolver.libraryFor(rendererAsset));
}

Future<void> testMustachioBuilder(String sourceLibraryContent,
{String libraryFrontMatter = _libraryFrontMatter,
Map<String, Object> outputs}) async {
sourceLibraryContent = '''
$libraryFrontMatter
$sourceLibraryContent
''';
await testBuilder(
mustachioBuilder(BuilderOptions({})),
{
..._annotationsAsset,
'foo|lib/foo.dart': sourceLibraryContent,
},
outputs: outputs,
writer: writer,
);
}

setUp(() {
writer = InMemoryAssetWriter();
});

test('builds a renderer for a class which extends Object', () async {
await testMustachioBuilder('''
class Foo {}
''',
}, outputs: {
''', outputs: {
'foo|lib/foo.renderers.dart': _containsAllOf(
// The requested 'renderFoo' function
'''
Expand Down Expand Up @@ -86,60 +114,39 @@ class _Renderer_Object extends RendererBase<Object> {
});

test('builds renderers from multiple annotations', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
await testMustachioBuilder('''
class Foo {}
class Bar {}
''', libraryFrontMatter: '''
@Renderer(#renderFoo, Context<Foo>(), 'foo.html.mustache')
@Renderer(#renderBar, Context<Bar>(), 'bar.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';
''');
var renderersLibrary = await resolveGeneratedLibrary(writer);

class Foo {}
class Bar {}
''',
}, outputs: {
'foo|lib/foo.renderers.dart': _containsAllOf(
// The requested 'renderFoo' function
'String renderFoo(Foo context, List<MustachioNode> ast)',
// The renderer class for Foo
'class _Renderer_Foo extends RendererBase<Foo>',
// The requested 'renderBar' function
'String renderBar(Bar context, List<MustachioNode> ast)',
// The renderer class for Bar
'class _Renderer_Bar extends RendererBase<Bar>')
});
expect(renderersLibrary.getTopLevelFunction('renderFoo'), isNotNull);
expect(renderersLibrary.getTopLevelFunction('renderBar'), isNotNull);
expect(renderersLibrary.getType('_Renderer_Foo'), isNotNull);
expect(renderersLibrary.getType('_Renderer_Bar'), isNotNull);
});

test('builds a renderer for a class which extends another class', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';

test('builds a renderer for a class which is extended by a rendered class',
() async {
await testMustachioBuilder('''
class FooBase {}

class Foo extends FooBase {}
''',
}, outputs: {
'foo|lib/foo.renderers.dart': _containsAllOf(
'String _render_FooBase(FooBase context, List<MustachioNode> ast)',
'class _Renderer_FooBase extends RendererBase<FooBase>')
});
''');
var renderersLibrary = await resolveGeneratedLibrary(writer);

expect(renderersLibrary.getTopLevelFunction('_render_FooBase'), isNotNull);
expect(renderersLibrary.getType('_Renderer_FooBase'), isNotNull);
});

test('builds a renderer for a generic type', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';

await testMustachioBuilder('''
class Foo<T> {}
''',
}, outputs: {
''', outputs: {
'foo|lib/foo.renderers.dart': _containsAllOf(
// The requested 'renderFoo' function
'String renderFoo<T>(Foo<T> context, List<MustachioNode> ast)',
Expand All @@ -149,100 +156,69 @@ class Foo<T> {}
});

test('builds a renderer for a type found in a getter', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';

await testMustachioBuilder('''
abstract class Foo {
Bar get bar;
}

class Bar {}
''',
}, outputs: {
'foo|lib/foo.renderers.dart': _containsAllOf(
// The render function for Bar
'String _render_Bar(Bar context, List<MustachioNode> ast)',
// The renderer class for Bar
'class _Renderer_Bar extends RendererBase<Bar>')
});
''');
var renderersLibrary = await resolveGeneratedLibrary(writer);

expect(renderersLibrary.getTopLevelFunction('_render_Bar'), isNotNull);
expect(renderersLibrary.getType('_Renderer_Bar'), isNotNull);
});

test('skips a type found in a static or private getter', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';

await testMustachioBuilder('''
class Foo {
static Bar get bar1 => Bar();
Bar get _bar2 => Bar();
}

class Bar {}
''',
}, outputs: {
'foo|lib/foo.renderers.dart': _containsNoneOf(
// No render function for Bar
'String _render_Bar',
// No renderer class for Bar
'class _Renderer_Bar extends RendererBase<Bar>')
});
''');
var renderersLibrary = await resolveGeneratedLibrary(writer);

expect(renderersLibrary.getTopLevelFunction('_render_Bar'), isNull);
expect(renderersLibrary.getType('_Renderer_Bar'), isNull);
});

test('skips a type found in a setter or method', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';

await testMustachioBuilder('''
abstract class Foo {
void set bar1(Bar b);
Bar bar2(Bar b);
}

class Bar {}
''',
}, outputs: {
'foo|lib/foo.renderers.dart': _containsNoneOf(
// No render function for Bar
'String _render_Bar',
// No renderer class for Bar
'class _Renderer_Bar extends RendererBase<Bar>')
});
''');
var renderersLibrary = await resolveGeneratedLibrary(writer);

expect(renderersLibrary.getTopLevelFunction('_render_Bar'), isNull);
expect(renderersLibrary.getType('_Renderer_Bar'), isNull);
});

test('builds a renderer for a generic, bounded type', () async {
await testBuilder(mustachioBuilder(BuilderOptions({})), {
..._annotationsAsset,
'foo|lib/foo.dart': '''
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
library foo;
import 'package:mustachio/annotations.dart';

await testMustachioBuilder('''
class Foo<T extends num> {}
''',
}, outputs: {
'foo|lib/foo.renderers.dart': _containsAllOf(
// The requested 'renderFoo' function
'String renderFoo<T extends num>(Foo<T> context, List<MustachioNode> ast)',
// The renderer class for Foo
'''
class _Renderer_Foo<T extends num> extends RendererBase<Foo<T>> {
_Renderer_Foo(Foo<T> context) : super(context);
}
''',
// The render function for num, found in Foo's type parameter bound
'String _render_num(num context, List<MustachioNode> ast)',
// The renderer class for num
'class _Renderer_num extends RendererBase<num>')
});
''');
var renderersLibrary = await resolveGeneratedLibrary(writer);

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'));

expect(renderersLibrary.getTopLevelFunction('_render_num'), isNotNull);
expect(renderersLibrary.getType('_Renderer_num'), isNotNull);
});
}

extension on LibraryElement {
FunctionElement getTopLevelFunction(String name) => topLevelElements
.whereType<FunctionElement>()
.firstWhere((element) => element.name == name, orElse: () => null);
}