@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 typeConverter() { 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 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 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 get primaryKey => {id}; } class PrimaryKeyAndAutoIncrement extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get other => text()(); @override Set get primaryKey => {other}; } class DynamicConverter extends Table { TextColumn get a1 => text().map(typeConverter())(); TextColumn get a2 => text().map(typeConverter())(); TextColumn get b1 => text().map>(typeConverter, String>())(); TextColumn get b2 => text().map>(typeConverter, String>())(); TextColumn get c => text().map>(typeConverter, 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 parse(String name) async { final result = state = await backend.analyze('package:a/main.dart'); return result.analyzedElements .whereType() .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().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'), ]), ); }); }