Skip to content
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

feat: add new command to get text #1

Merged
merged 2 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
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
File renamed without changes.
180 changes: 180 additions & 0 deletions lib/get_text_command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_driver/src/common/find.dart';
import 'package:flutter_driver/src/common/message.dart';
import 'package:flutter_test/flutter_test.dart';

class Base64URL {
static String encode(String str) {
String base64 = base64Encode(utf8.encode(str));
return base64.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
}

static String decode(String str) {
String base64 = str.replaceAll('-', '+').replaceAll('_', '/');

// Add padding if needed
switch (base64.length % 4) {
case 2:
base64 += '==';
break;
case 3:
base64 += '=';
break;
}

return utf8.decode(base64Decode(base64));
}
}

class FinderHelper {
static SerializableFinder deserializeBase64(String base64Str) {
try {
// Decode base64 to JSON string
final jsonStr = Base64URL.decode(base64Str);

// Parse JSON
final dynamic finderData = json.decode(jsonStr);

if (finderData is! Map<String, dynamic>) {
throw Exception('finder is not valid');
}

if (!finderData.containsKey('finderType')) {
throw Exception('Invalid finder format: missing finderType');
}

final String finderType = finderData['finderType'] as String;

switch (finderType) {
case 'ByText':
return ByText(finderData['text'] as String);

case 'ByType':
return ByType(finderData['type'] as String);

case 'ByValueKey':
final keyType = finderData['keyValueType'] as String?;
final keyValue = finderData['keyValueString'] as String;

if (keyType == 'int') {
return ByValueKey(int.parse(keyValue));
}
return ByValueKey(keyValue);

case 'Ancestor':
// Parse of and matching which are JSON strings
final ofJson = json.decode(finderData['of'] as String);
final matchingJson = json.decode(finderData['matching'] as String);

return Ancestor(
of: deserializeBase64(Base64URL.encode(json.encode(ofJson))),
matching:
deserializeBase64(Base64URL.encode(json.encode(matchingJson))),
matchRoot: finderData['matchRoot'] == 'true',
firstMatchOnly: finderData['firstMatchOnly'] == 'true',
);

case 'Descendant':
final ofJson = json.decode(finderData['of'] as String);
final matchingJson = json.decode(finderData['matching'] as String);

return Descendant(
of: deserializeBase64(Base64URL.encode(json.encode(ofJson))),
matching:
deserializeBase64(Base64URL.encode(json.encode(matchingJson))),
matchRoot: finderData['matchRoot'] == 'true',
firstMatchOnly: finderData['firstMatchOnly'] == 'true',
);

default:
throw Exception('Unsupported finder type: $finderType');
}
} catch (e) {
throw Exception('Error deserializing finder: $e');
}
}
}

class GetTextCommandExtension extends CommandExtension {
String? getTextFromWidget(Text widget) {
return widget.data ?? widget.textSpan?.toPlainText();
}

@override
Future<Result> call(
Command command,
WidgetController prober,
CreateFinderFactory finderFactory,
CommandHandlerFactory handlerFactory) async {
final GetTextCommand dragCommand = command as GetTextCommand;

// Create finder for Text widget
final type = dragCommand.base64Element;
// decodeBase64 to json
SerializableFinder serializableFinder =
FinderHelper.deserializeBase64(type);

final Finder finder = finderFactory.createFinder(serializableFinder);

// Get the widget element
final Element element = prober.element(finder);

// if element is not a Text widget, return false with error
if (element.widget is! Text) {
return const GetTextResult(false, data: {
'errorCode': 'NOT_A_TEXT_WIDGET',
'error': 'Found element is not a Text widget'
});
}

final text = getTextFromWidget(element.widget as Text);
return text != null
? GetTextResult(true, data: {'text': text})
: const GetTextResult(false, data: {
'errorCode': 'NO_TEXT_CONTENT',
'error': 'No text content found'
});
}

@override
String get commandKind => 'getTextWithCommandExtension';

@override
Command deserialize(
Map<String, String> params,
DeserializeFinderFactory finderFactory,
DeserializeCommandFactory commandFactory) {
return GetTextCommand.deserialize(params);
}
}

class GetTextCommand extends Command {
final String base64Element;

GetTextCommand(this.base64Element);

@override
String get kind => 'getTextWithCommandExtension';

GetTextCommand.deserialize(Map<String, String> params)
: base64Element = params['findBy']!;
}

class GetTextResult extends Result {
final bool success;
final Map<String, dynamic>? data;

const GetTextResult(this.success, {this.data});

@override
Map<String, dynamic> toJson() {
return <String, dynamic>{
'success': success,
if (data != null) ...data!,
};
}
}
80 changes: 60 additions & 20 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_driver/src/extension/extension.dart';

import 'extended_commands.dart';

import 'drag_commands.dart';
import 'get_text_command.dart';

void main() {
enableFlutterDriverExtension(
commands: [DragCommandExtension()]);
enableFlutterDriverExtension(commands: [DragCommandExtension(), GetTextCommandExtension()]);
runApp(const MyApp());
}

Expand All @@ -29,6 +28,7 @@ class MyApp extends StatelessWidget {

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});

final String title;

@override
Expand All @@ -45,22 +45,62 @@ class _MyHomePageState extends State<MyHomePage> {
title: Text(widget.title),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ReorderableListView(
padding: const EdgeInsets.all(8),
children: items.map((item) {
return ListTile(
key: ValueKey(item),
title: Text(item),
tileColor: Colors.grey.shade200,
);
}).toList(),
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) newIndex -= 1;
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
body: Column(
children: [
// Original ReorderableListView
Expanded(
child: ReorderableListView(
padding: const EdgeInsets.all(8),
children: items.map((item) {
return ListTile(
key: ValueKey(item),
title: Text(item),
tileColor: Colors.grey.shade200,
);
}).toList(),
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) newIndex -= 1;
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
),
),
// TextSpan example
const Padding(
padding: EdgeInsets.all(16.0),
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: 'Amount: ',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
TextSpan(
text: '100',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
TextSpan(
text: ' USD',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
key: ValueKey('amount'),
),
),
],
),
);
}
Expand Down
2 changes: 1 addition & 1 deletion test_driver/app.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:demo/extended_commands.dart';
import 'package:demo/drag_commands.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:demo/main.dart' as app;

Expand Down