import 'package:mockito/mockito.dart'; import 'package:moor/moor.dart'; import 'package:test/test.dart'; import 'data/tables/todos.dart'; import 'data/utils/mocks.dart'; void main() { late TodoDb db; late MockExecutor executor; late MockStreamQueries streamQueries; setUp(() { executor = MockExecutor(); streamQueries = MockStreamQueries(); final connection = createConnection(executor, streamQueries); db = TodoDb.connect(connection); }); test('generates insert statements', () async { await db.into(db.todosTable).insert(const TodosTableCompanion( content: Value('Implement insert statements'), title: Value.absent(), )); verify(executor.runInsert('INSERT INTO todos (content) VALUES (?)', ['Implement insert statements'])); }); test('can insert floating point values', () async { // regression test for https://github.com/simolus3/moor/issues/30 await db.into(db.tableWithoutPK).insert(TableWithoutPKData( notReallyAnId: 42, someFloat: 3.1415, custom: MyCustomObject('custom'))); verify(executor.runInsert( 'INSERT INTO table_without_p_k ' '(not_really_an_id, some_float, custom) VALUES (?, ?, ?)', [42, 3.1415, anything])); }); test('generates insert or replace statements', () async { await db.into(db.todosTable).insert( TodoEntry( id: 113, content: 'Done', ), mode: InsertMode.insertOrReplace); verify(executor.runInsert( 'INSERT OR REPLACE INTO todos (id, content) VALUES (?, ?)', [113, 'Done'])); }); test('generates DEFAULT VALUES statement when otherwise empty', () async { await db.into(db.pureDefaults).insert(const PureDefaultsCompanion()); verify(executor.runInsert('INSERT INTO pure_defaults DEFAULT VALUES', [])); }); test('notifies stream queries on inserts', () async { await db.into(db.users).insert(UsersCompanion( name: const Value('User McUserface'), isAwesome: const Value(true), profilePicture: Value(Uint8List(0)), )); verify(streamQueries.handleTableUpdates( {const TableUpdate('users', kind: UpdateKind.insert)})); }); group('enforces integrity', () { test('for regular inserts', () async { InvalidDataException exception; try { await db.into(db.todosTable).insert( const TodosTableCompanion( // has a min length of 4 title: Value('s'), ), ); fail('inserting invalid data did not throw'); } on InvalidDataException catch (e) { exception = e; } expect(exception.toString(), startsWith('InvalidDataException')); }); test("for upserts that aren't valid inserts", () { expect( () { return db .into(db.todosTable) // content would be required .insertOnConflictUpdate(const TodosTableCompanion()); }, throwsA(isA()), ); }); test("for upserts that aren't valid updates", () { expect( () { final insert = TodosTableCompanion.insert(content: 'content'); const update = TodosTableCompanion(title: Value('s')); return db .into(db.todosTable) .insert(insert, onConflict: DoUpdate((_) => update)); }, throwsA(isA()), ); }); }); test('reports auto-increment id', () { when(executor.runInsert(any, any)).thenAnswer((_) => Future.value(42)); expect( db .into(db.todosTable) .insert(const TodosTableCompanion(content: Value('Bottom text'))), completion(42), ); }); test('evaluates client-default functions', () async { await db.into(db.tableWithoutPK).insert( TableWithoutPKCompanion.insert(notReallyAnId: 3, someFloat: 3.14)); // the client default generates a uuid final uuidRegex = RegExp( r'[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}'); verify(executor.runInsert( 'INSERT INTO table_without_p_k (not_really_an_id, some_float, custom) ' 'VALUES (?, ?, ?)', [3, 3.14, matches(uuidRegex)], )); }); test('escaped when column name is keyword', () async { await db .into(db.pureDefaults) .insert(PureDefaultsCompanion.insert(txt: const Value('foo'))); verify(executor .runInsert('INSERT INTO pure_defaults ("insert") VALUES (?)', ['foo'])); }); test('can insert custom companions', () async { await db.into(db.users).insert(UsersCompanion.custom( isAwesome: const Constant(true), name: const Variable('User name'), profilePicture: const CustomExpression('_custom_'), creationTime: currentDateAndTime)); verify( executor.runInsert( 'INSERT INTO users (name, is_awesome, profile_picture, creation_time) ' "VALUES (?, 1, _custom_, strftime('%s', CURRENT_TIMESTAMP))", ['User name'], ), ); }); test('can use an upsert clause', () async { await db.into(db.todosTable).insert( TodosTableCompanion.insert(content: 'my content'), onConflict: DoUpdate((old) { return TodosTableCompanion.custom( content: const Variable('important: ') + old.content); }), ); verify(executor.runInsert( 'INSERT INTO todos (content) VALUES (?) ' 'ON CONFLICT(id) DO UPDATE SET content = ? || content', argThat(equals(['my content', 'important: '])), )); }); test('can use a custom conflict clause', () async { await db.into(db.todosTable).insert( TodosTableCompanion.insert(content: 'my content'), onConflict: DoUpdate( (old) => TodosTableCompanion.insert(content: 'changed'), target: [db.todosTable.content], ), ); verify(executor.runInsert( 'INSERT INTO todos (content) VALUES (?) ' 'ON CONFLICT(content) DO UPDATE SET content = ?', argThat(equals(['my content', 'changed'])), )); }); test('insertOnConflictUpdate', () async { when(executor.runInsert(any, any)).thenAnswer((_) => Future.value(3)); final id = await db.into(db.todosTable).insertOnConflictUpdate( TodosTableCompanion.insert(content: 'content', id: const Value(3))); verify(executor.runInsert( 'INSERT INTO todos (id, content) VALUES (?, ?) ' 'ON CONFLICT(id) DO UPDATE SET id = ?, content = ?', [3, 'content', 3, 'content'], )); expect(id, 3); }); test('applies implicit type converter', () async { await db.into(db.categories).insert(CategoriesCompanion.insert( description: 'description', priority: const Value(CategoryPriority.medium), )); verify(executor.runInsert( 'INSERT INTO categories ("desc", priority) VALUES (?, ?)', ['description', 1], )); }); }