mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at ft/monetization 265 lines 10 kB view raw
1import 'package:atproto/com_atproto_repo_listrecords.dart'; 2import 'package:atproto_core/atproto_core.dart'; 3import 'package:bloc_test/bloc_test.dart'; 4import 'package:flutter/material.dart'; 5import 'package:flutter_bloc/flutter_bloc.dart'; 6import 'package:flutter_test/flutter_test.dart'; 7import 'package:lazurite/features/devtools/cubit/dev_tools_cubit.dart'; 8import 'package:lazurite/features/devtools/presentation/dev_tools_screen.dart'; 9import 'package:mocktail/mocktail.dart'; 10 11class MockDevToolsCubit extends MockCubit<DevToolsState> implements DevToolsCubit {} 12 13class FakeDevToolsState extends Fake implements DevToolsState {} 14 15late MockDevToolsCubit mockDevToolsCubit; 16 17void main() { 18 setUpAll(() { 19 registerFallbackValue(FakeDevToolsState()); 20 registerFallbackValue( 21 const RepoListRecordsRecord( 22 uri: AtUri('at://did:plc:test/app.bsky.feed.post/123'), 23 cid: 'cid123', 24 value: {'text': 'Test'}, 25 ), 26 ); 27 }); 28 29 setUp(() { 30 mockDevToolsCubit = MockDevToolsCubit(); 31 32 when(() => mockDevToolsCubit.state).thenReturn(const DevToolsState()); 33 when(() => mockDevToolsCubit.resolve(any())).thenAnswer((_) async {}); 34 when(() => mockDevToolsCubit.loadCollection(any())).thenAnswer((_) async {}); 35 when(() => mockDevToolsCubit.loadRecord(any())).thenAnswer((_) async {}); 36 when(() => mockDevToolsCubit.loadMoreRecords()).thenAnswer((_) async {}); 37 when(() => mockDevToolsCubit.goBackToRepo()).thenReturn(null); 38 when(() => mockDevToolsCubit.goBackToCollection()).thenReturn(null); 39 when(() => mockDevToolsCubit.clearInput()).thenReturn(null); 40 41 whenListen(mockDevToolsCubit, const Stream<DevToolsState>.empty(), initialState: const DevToolsState()); 42 }); 43 44 Widget buildSubject({String? initialQuery}) { 45 return MaterialApp( 46 home: BlocProvider<DevToolsCubit>.value( 47 value: mockDevToolsCubit, 48 child: DevToolsScreen(initialQuery: initialQuery), 49 ), 50 ); 51 } 52 53 group('DevToolsScreen', () { 54 testWidgets('renders empty state initially', (tester) async { 55 await tester.pumpWidget(buildSubject()); 56 57 expect(find.text('PDS Explorer'), findsAtLeastNWidgets(1)); 58 expect(find.textContaining('Enter a handle, DID, or AT-URI'), findsOneWidget); 59 expect(find.text('Inspired by pds.ls'), findsOneWidget); 60 }); 61 62 testWidgets('renders loading state', (tester) async { 63 when(() => mockDevToolsCubit.state).thenReturn(const DevToolsState(status: DevToolsStatus.loading)); 64 whenListen( 65 mockDevToolsCubit, 66 const Stream<DevToolsState>.empty(), 67 initialState: const DevToolsState(status: DevToolsStatus.loading), 68 ); 69 70 await tester.pumpWidget(buildSubject()); 71 72 expect(find.byType(CircularProgressIndicator), findsOneWidget); 73 }); 74 75 testWidgets('renders error state', (tester) async { 76 when( 77 () => mockDevToolsCubit.state, 78 ).thenReturn(const DevToolsState(status: DevToolsStatus.error, errorMessage: 'Test error')); 79 whenListen( 80 mockDevToolsCubit, 81 const Stream<DevToolsState>.empty(), 82 initialState: const DevToolsState(status: DevToolsStatus.error, errorMessage: 'Test error'), 83 ); 84 85 await tester.pumpWidget(buildSubject()); 86 87 expect(find.text('Error'), findsOneWidget); 88 expect(find.text('Test error'), findsOneWidget); 89 }); 90 91 testWidgets('renders repo overview with collection counts', (tester) async { 92 const state = DevToolsState( 93 status: DevToolsStatus.repoLoaded, 94 did: 'did:plc:test', 95 handle: 'test.bsky.social', 96 repoHandle: 'test.bsky.social', 97 collections: [ 98 CollectionSummary('app.bsky.feed.post', recordCount: 2), 99 CollectionSummary('app.bsky.feed.like', recordCount: 3), 100 ], 101 ); 102 103 when(() => mockDevToolsCubit.state).thenReturn(state); 104 whenListen(mockDevToolsCubit, const Stream<DevToolsState>.empty(), initialState: state); 105 106 await tester.pumpWidget(buildSubject()); 107 108 expect(find.text('test.bsky.social'), findsAtLeastNWidgets(1)); 109 expect(find.text('did:plc:test'), findsOneWidget); 110 expect(find.text('2 collections'), findsOneWidget); 111 expect(find.text('5 records'), findsOneWidget); 112 expect(find.text('app.bsky.feed.post'), findsOneWidget); 113 expect(find.text('app.bsky.feed.like'), findsOneWidget); 114 }); 115 116 testWidgets('submitting search calls cubit resolve', (tester) async { 117 await tester.pumpWidget(buildSubject()); 118 119 await tester.enterText(find.byType(TextField), 'alice.bsky.social'); 120 await tester.tap(find.text('Resolve')); 121 122 verify(() => mockDevToolsCubit.resolve('alice.bsky.social')).called(1); 123 }); 124 125 testWidgets('initial query prefills the input and resolves automatically', (tester) async { 126 await tester.pumpWidget(buildSubject(initialQuery: 'did:plc:test')); 127 await tester.pump(); 128 129 expect(find.widgetWithText(TextField, 'did:plc:test'), findsOneWidget); 130 verify(() => mockDevToolsCubit.resolve('did:plc:test')).called(1); 131 }); 132 133 testWidgets('tapping a record calls cubit loadRecord', (tester) async { 134 const record = RepoListRecordsRecord( 135 uri: AtUri('at://did:plc:test/app.bsky.feed.post/123'), 136 cid: 'cid123', 137 value: {'text': 'Summary'}, 138 ); 139 const state = DevToolsState( 140 status: DevToolsStatus.collectionLoaded, 141 did: 'did:plc:test', 142 handle: 'test.bsky.social', 143 repoHandle: 'test.bsky.social', 144 collections: [CollectionSummary('app.bsky.feed.post', recordCount: 1)], 145 selectedCollection: 'app.bsky.feed.post', 146 records: [record], 147 ); 148 149 when(() => mockDevToolsCubit.state).thenReturn(state); 150 whenListen(mockDevToolsCubit, const Stream<DevToolsState>.empty(), initialState: state); 151 152 await tester.pumpWidget(buildSubject()); 153 await tester.tap(find.text('123')); 154 155 verify(() => mockDevToolsCubit.loadRecord(record)).called(1); 156 }); 157 158 testWidgets('renders breadcrumbs for record navigation', (tester) async { 159 const state = DevToolsState( 160 status: DevToolsStatus.recordLoaded, 161 did: 'did:plc:test', 162 handle: 'test.bsky.social', 163 repoHandle: 'test.bsky.social', 164 collections: [CollectionSummary('app.bsky.feed.post', recordCount: 1)], 165 selectedCollection: 'app.bsky.feed.post', 166 selectedRecord: RecordInfo( 167 uri: 'at://did:plc:test/app.bsky.feed.post/123', 168 cid: 'cid123', 169 value: {'text': 'Summary'}, 170 ), 171 ); 172 173 when(() => mockDevToolsCubit.state).thenReturn(state); 174 whenListen(mockDevToolsCubit, const Stream<DevToolsState>.empty(), initialState: state); 175 176 await tester.pumpWidget(buildSubject()); 177 178 expect(find.byKey(const ValueKey('dev-tools-breadcrumb-repo')), findsOneWidget); 179 expect(find.byKey(const ValueKey('dev-tools-breadcrumb-collection')), findsOneWidget); 180 expect(find.byKey(const ValueKey('dev-tools-breadcrumb-record')), findsOneWidget); 181 expect(find.text('test.bsky.social'), findsAtLeastNWidgets(1)); 182 expect(find.text('app.bsky.feed.post'), findsAtLeastNWidgets(1)); 183 expect(find.text('123'), findsAtLeastNWidgets(1)); 184 }); 185 186 testWidgets('tapping repo breadcrumb calls cubit goBackToRepo', (tester) async { 187 const state = DevToolsState( 188 status: DevToolsStatus.collectionLoaded, 189 did: 'did:plc:test', 190 handle: 'test.bsky.social', 191 repoHandle: 'test.bsky.social', 192 collections: [CollectionSummary('app.bsky.feed.post', recordCount: 1)], 193 selectedCollection: 'app.bsky.feed.post', 194 records: [ 195 RepoListRecordsRecord( 196 uri: AtUri('at://did:plc:test/app.bsky.feed.post/123'), 197 cid: 'cid123', 198 value: {'text': 'Summary'}, 199 ), 200 ], 201 ); 202 203 when(() => mockDevToolsCubit.state).thenReturn(state); 204 whenListen(mockDevToolsCubit, const Stream<DevToolsState>.empty(), initialState: state); 205 206 await tester.pumpWidget(buildSubject()); 207 await tester.tap(find.byKey(const ValueKey('dev-tools-breadcrumb-repo'))); 208 209 verify(() => mockDevToolsCubit.goBackToRepo()).called(1); 210 }); 211 212 testWidgets('tapping collection breadcrumb calls cubit goBackToCollection', (tester) async { 213 const state = DevToolsState( 214 status: DevToolsStatus.recordLoaded, 215 did: 'did:plc:test', 216 handle: 'test.bsky.social', 217 repoHandle: 'test.bsky.social', 218 collections: [CollectionSummary('app.bsky.feed.post', recordCount: 1)], 219 selectedCollection: 'app.bsky.feed.post', 220 selectedRecord: RecordInfo( 221 uri: 'at://did:plc:test/app.bsky.feed.post/123', 222 cid: 'cid123', 223 value: {'text': 'Summary'}, 224 ), 225 ); 226 227 when(() => mockDevToolsCubit.state).thenReturn(state); 228 whenListen(mockDevToolsCubit, const Stream<DevToolsState>.empty(), initialState: state); 229 230 await tester.pumpWidget(buildSubject()); 231 await tester.tap(find.byKey(const ValueKey('dev-tools-breadcrumb-collection'))); 232 233 verify(() => mockDevToolsCubit.goBackToCollection()).called(1); 234 }); 235 236 testWidgets('shows breadcrumb progress without full-screen spinner during record navigation', (tester) async { 237 const state = DevToolsState( 238 status: DevToolsStatus.collectionLoaded, 239 did: 'did:plc:test', 240 handle: 'test.bsky.social', 241 repoHandle: 'test.bsky.social', 242 collections: [CollectionSummary('app.bsky.feed.post', recordCount: 1)], 243 selectedCollection: 'app.bsky.feed.post', 244 records: [ 245 RepoListRecordsRecord( 246 uri: AtUri('at://did:plc:test/app.bsky.feed.post/123'), 247 cid: 'cid123', 248 value: {'text': 'Summary'}, 249 ), 250 ], 251 isRecordLoading: true, 252 ); 253 254 when(() => mockDevToolsCubit.state).thenReturn(state); 255 whenListen(mockDevToolsCubit, const Stream<DevToolsState>.empty(), initialState: state); 256 257 await tester.pumpWidget(buildSubject()); 258 259 expect(find.byKey(const ValueKey('app-breadcrumbs-loading')), findsOneWidget); 260 expect(find.byType(LinearProgressIndicator), findsOneWidget); 261 expect(find.byType(CircularProgressIndicator), findsNothing); 262 expect(find.text('123'), findsOneWidget); 263 }); 264 }); 265}