drift/drift_dev/test/analysis/resolver/dart/table_parser_test.dart

329 lines
10 KiB
Dart

@Tags(['analyzer'])
import 'package:collection/collection.dart';
import 'package:drift_dev/src/analysis/driver/state.dart';
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:test/test.dart';
import '../../test_utils.dart';
void main() {
late TestBackend backend;
late FileState state;
setUpAll(() async {
backend = await TestBackend.init({
'a|lib/main.dart': r'''
import 'package:drift/drift.dart';
TypeConverter<Dart, SQL> typeConverter<Dart, SQL>() {
throw 'stub';
}
class TableWithCustomName extends Table {
@override String get tableName => 'my-fancy-table';
@override bool get withoutRowId => true;
}
class Users extends Table {
/// The user id
IntColumn get id => integer().autoIncrement()();
/// The username
///
/// The username must be between 6-32 characters
TextColumn get name => text().named("user_name").withLength(min: 6, max: 32)();
TextColumn get onlyMax => text().withLength(max: 100)();
DateTimeColumn get defaults => dateTime().withDefault(currentDate)();
}
class CustomPrimaryKey extends Table {
IntColumn get partA => integer()();
IntColumn get partB => integer().customConstraint('custom')();
@override
Set<Column> get primaryKey => {partA, partB};
}
class WrongName extends Table {
String constructTableName() => 'my-table-name';
String get tableName => constructTableName();
}
mixin IntIdTable on Table {
IntColumn get id => integer().autoIncrement()();
}
abstract class HasNameTable extends Table {
TextColumn get name => text()();
}
class Foos extends HasNameTable with IntIdTable {
TextColumn get name => text().nullable()();
}
class Socks extends Table {
TextColumn get name => text()();
IntColumn get id => integer()();
@override
Set<Column> get primaryKey => {id};
}
class ArchivedSocks extends Socks {
TextColumn get archivedBy => text()();
DateTimeColumn get archivedOn => dateTime()();
}
class WithAliasForRowId extends Table {
IntColumn get id => integer()();
TextColumn get name => text()();
@override
Set<Column> get primaryKey => {id};
}
class PrimaryKeyAndAutoIncrement extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get other => text()();
@override
Set<Column> get primaryKey => {other};
}
class DynamicConverter extends Table {
TextColumn get a1 => text().map<dynamic>(typeConverter<dynamic, String>())();
TextColumn get a2 => text().map<DoesNotExist>(typeConverter<DoesNotExist, String>())();
TextColumn get b1 => text().map<List<dynamic>>(typeConverter<List<dynamic>, String>())();
TextColumn get b2 => text().map<List<DoesNotExist>>(typeConverter<List<DoesNotExist>, String>())();
TextColumn get c => text().map<Map<String, dynamic>>(typeConverter<Map<String, dynamic>, String>())();
}
''',
'a|lib/invalid_reference.dart': '''
import 'package:drift/drift.dart';
class Foo extends Table {
IntColumn get id => integer().autoIncrement()();
}
@DriftDatabase(tables: [Foo, DoesNotExist])
class Database {}
@DriftAccessor(views: [DoesNotExist])
class Accessor {}
''',
'a|lib/invalid_constraints.dart': '''
import 'package:drift/drift.dart';
class InvalidConstraints extends Table {
IntColumn get a => integer().autoIncrement().customConstraint('foo')();
IntColumn get b => integer().customConstraint('a').customConstraint('b')();
}
''',
});
});
tearDownAll(() => backend.dispose());
Future<DriftTable?> parse(String name) async {
final result = state = await backend.analyze('package:a/main.dart');
return result.analyzedElements
.whereType<DriftTable>()
.firstWhereOrNull((e) => e.baseDartName == name);
}
group('table names', () {
test('use overridden name', () async {
final parsed = await parse('TableWithCustomName');
expect(parsed!.schemaName, equals('my-fancy-table'));
expect(parsed.withoutRowId, isTrue);
});
test('use re-cased class name', () async {
final parsed = await parse('Users');
expect(parsed!.schemaName, equals('users'));
});
test('should not parse for complex methods', () async {
await parse('WrongName');
expect(state.allErrors, isNotEmpty);
});
});
group('Columns', () {
test('should use field name if no name has been set explicitly', () async {
final table = await parse('Users');
final idColumn =
table!.columns.singleWhere((col) => col.nameInSql == 'id');
expect(
idColumn.declaration,
isA<DriftDeclaration>().having((c) => c.name, 'name', 'id'),
);
});
test('should use explicit name, if it exists', () async {
final table = await parse('Users');
table!.columns.singleWhere((col) => col.nameInSql == 'user_name');
});
test('should parse min and max length for text columns', () async {
final table = await parse('Users');
final idColumn =
table!.columns.singleWhere((col) => col.nameInSql == 'user_name');
expect(idColumn.constraints,
contains(LimitingTextLength(minLength: 6, maxLength: 32)));
});
test('should only parse max length when relevant', () async {
final table = await parse('Users');
final idColumn =
table!.columns.singleWhere((col) => col.nameInDart == 'onlyMax');
expect(
idColumn.constraints, contains(LimitingTextLength(maxLength: 100)));
});
test('parses custom constraints', () async {
final table = await parse('CustomPrimaryKey');
final partA = table!.columns.singleWhere((c) => c.nameInDart == 'partA');
final partB = table.columns.singleWhere((c) => c.nameInDart == 'partB');
expect(partB.customConstraints, 'custom');
expect(partA.customConstraints, isNull);
});
test('parsed default values', () async {
final table = await parse('Users');
final defaultsColumn =
table!.columns.singleWhere((c) => c.nameInSql == 'defaults');
expect(defaultsColumn.defaultArgument.toString(), 'currentDate');
});
test('parses documentation comments', () async {
final table = await parse('Users');
final idColumn =
table!.columns.singleWhere((col) => col.nameInSql == 'id');
final usernameColumn =
table.columns.singleWhere((col) => col.nameInSql == 'user_name');
expect(idColumn.documentationComment, '/// The user id');
expect(
usernameColumn.documentationComment,
'/// The username\n///\n/// The username must be between 6-32 characters',
);
});
});
test('parses custom primary keys', () async {
final table = await parse('CustomPrimaryKey');
expect(table!.fullPrimaryKey, containsAll(table.columns));
expect(
table.columns.any(
(column) => column.constraints.any((e) => e is PrimaryKeyColumn)),
isFalse,
);
});
test('warns when using primaryKey and autoIncrement()', () async {
await parse('PrimaryKeyAndAutoIncrement');
expect(
state.allErrors,
contains(isDriftError(
contains('override primaryKey and use autoIncrement()'))),
);
});
test('recognizes aliases for rowid', () async {
final table = await parse('WithAliasForRowId');
final idColumn = table!.columns.singleWhere((c) => c.nameInSql == 'id');
expect(table.isColumnRequiredForInsert(idColumn), isFalse);
});
group('inheritance', () {
test('from abstract classes or mixins', () async {
final table = await parse('Foos');
expect(table!.columns, hasLength(2));
expect(
table.columns.map((c) => c.nameInSql), containsAll(['id', 'name']));
});
test('from regular classes', () async {
final socks = await parse('Socks');
final archivedSocks = await parse('ArchivedSocks');
expect(socks!.columns, hasLength(2));
expect(socks.columns.map((c) => c.nameInSql), ['name', 'id']);
expect(archivedSocks!.columns, hasLength(4));
expect(archivedSocks.columns.map((c) => c.nameInSql),
['name', 'id', 'archived_by', 'archived_on']);
expect(archivedSocks.fullPrimaryKey.map((e) => e.nameInSql), ['id']);
});
});
test('reports error when using autoIncrement and primaryKey', () async {
final uri = Uri.parse('package:a/main.dart');
final file = await backend.driver.fullyAnalyze(uri);
expect(
file.allErrors,
contains(
isDriftError(
"Tables can't override primaryKey and use autoIncrement()"),
),
);
});
test('reports errors for unknown classes', () async {
final uri = Uri.parse('package:a/invalid_reference.dart');
final file = await backend.driver.fullyAnalyze(uri);
expect(
file.allErrors,
containsAll([
isDriftError(allOf(
contains('Could not read tables from @DriftDatabase annotation!'),
contains('Please make sure that all table classes exist.'),
)),
isDriftError(allOf(
contains('Could not read views from @DriftAccessor annotation!'),
contains('Please make sure that all table classes exist.'),
)),
]),
);
});
test('reports errors around suspicous customConstraint uses', () async {
final uri = Uri.parse('package:a/invalid_constraints.dart');
final file = await backend.driver.fullyAnalyze(uri);
expect(
file.allErrors,
containsAll([
isDriftError(
allOf(
contains(
'This column definition is using both drift-defined constraints'),
contains('and a customConstraint()'),
),
).withSpan('a'),
isDriftError(
contains("You've already set custom constraints on this column"),
).withSpan('customConstraint'),
]),
);
});
}