Merge branch 'gherkin' of git.openprivacy.ca:cwtch.im/cwtch-ui into gherkin

This commit is contained in:
erinn 2022-01-31 14:43:42 -08:00
commit 6844118d0f
18 changed files with 160 additions and 2117 deletions

View File

@ -0,0 +1,44 @@
Feature: Basic Profile Management
Scenario: Error on Creating a Profile without a Display Name
Given I wait until the widget with type 'ProfileMgrView' is present
And I tap the button with tooltip "Add new profile"
Then I expect the text 'Display Name' to be present
And I expect the text 'New Password' to be present
And I expect the text 'Please enter a display name' to be absent
Then I tap the "button" widget with label "Add new profile"
And I expect the text 'Please enter a display name' to be present
And I take a screenshot
Scenario: Create Unencrypted Profile
Given I wait until the widget with type 'ProfileMgrView' is present
And I tap the button with tooltip "Add new profile"
Then I expect the text 'Display Name' to be present
And I expect the text 'New Password' to be present
And I take a screenshot
Then I tap the "passwordCheckBox" widget
And I expect the text 'New Password' to be absent
And I take a screenshot
Then I fill the "displayNameFormElement" field with "Alice (<h1>hello</h1>)"
Then I tap the "button" widget with label "Add new profile"
And I expect a "ProfileRow" widget with text "Alice (<h1>hello</h1>)"
And I take a screenshot
Then I tap the "ProfileRow" widget with label "Alice (<h1>hello</h1>)"
And I expect the text 'Alice (<h1>hello</h1>) » Conversations' to be present
And I take a screenshot
Scenario: Create Encrypted Profile
Given I wait until the widget with type 'ProfileMgrView' is present
And I tap the button with tooltip "Add new profile"
Then I expect the text 'Display Name' to be present
And I expect the text 'New Password' to be present
And I take a screenshot
Then I fill the "displayNameFormElement" field with "Alice (Encrypted)"
Then I fill the "passwordFormElement" field with "password1"
Then I fill the "confirmPasswordFormElement" field with "password1"
And I take a screenshot
Then I tap the "button" widget with label "Add new profile"
And I expect a "ProfileRow" widget with text "Alice (Encrypted)"
And I take a screenshot
Then I tap the "ProfileRow" widget with label "Alice (Encrypted)"
And I expect the text 'Alice (Encrypted) » Conversations' to be present
And I take a screenshot

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,96 +0,0 @@
//import 'package:flutter_gherkin/flutter_gherkin_integration_test.dart'; // notice new import name
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart';
import 'dart:io';
// The application under test.
import 'package:cwtch/main.dart' as app;
import 'package:glob/glob.dart';
import 'hooks/env.dart';
import 'steps/chat.dart';
import 'steps/files.dart';
import 'steps/form_elements.dart';
import 'steps/overrides.dart';
import 'steps/text.dart';
import 'steps/utils.dart';
part 'gherkin_suite_test.g.dart';
const REPLACED_BY_SCRIPT = <String>['integration_test/features/**.feature'];
@GherkinTestSuite(executionOrder: ExecutionOrder.alphabetical, featurePaths: <String>['./integration_test/features/05_p2p_chat/01_add_remove_block_archive.feature','./integration_test/features/05_p2p_chat/02_proto_invites.feature','./integration_test/features/05_p2p_chat/03_send_receive.feature','./integration_test/features/05_p2p_chat/04_special_messages.feature','./integration_test/features/05_p2p_chat/05_overlays_invite.feature','./integration_test/features/05_p2p_chat/06_overlays_file.feature','./integration_test/features/05_p2p_chat/07_overlays_image.feature'])
void main() {
final params = [
SwitchStateParameter(),
];
final steps = [
// chat elements
ExpectReply(),
// form elements
CheckSwitchState(),
CheckSwitchStateWithText(),
DropdownChoose(),
// utils
TakeScreenshot(),
// overrides
TapWidgetWithType(),
TapFirstWidget(),
WaitUntilTypeExists(),
ExpectTextToBePresent(),
ExpectWidgetWithTextWithin(),
WaitUntilTextExists(),
SwipeOnType(),
// text
TorVersionPresent(),
TooltipTap(),
// files
FolderExists(),
FileExists(),
];
var sb = StringBuffer();
sb..writeln("## Custom Parameters\n")
..writeln("| name | pattern |")
..writeln("| --- | --- |");
for (var i in params) {
sb..write("| ")..write(i.identifier)..write(" | ")..write(i.pattern.toString().replaceFirst("RegExp: pattern=","").replaceFirst(" flags=i",""))..writeln(" |");
}
sb..writeln("\n## Custom steps\n")
..writeln("| pattern |")
..writeln("| --- |");
for (var i in steps) {
sb.writeln(i.pattern.toString().replaceFirst("RegExp: pattern=", "| ").replaceFirst(" flags=", " |"));
}
var f = File("integration_test/CustomSteps.md");
f.writeAsString(sb.toString());
executeTestSuite(
FlutterTestConfiguration.DEFAULT([])
..reporters = [
StdoutReporter(MessageLevel.error)
..setWriteLineFn(print)
..setWriteFn(print),
ProgressReporter()
..setWriteLineFn(print)
..setWriteFn(print),
TestRunSummaryReporter()
..setWriteLineFn(print)
..setWriteFn(print),
JsonReporter(
writeReport: (_, __) => Future<void>.value(),
),
]
..customStepParameterDefinitions = [
SwitchStateParameter(),
]
..stepDefinitions = steps
..hooks = [
ResetCwtchEnvironment(),
AttachScreenshotOnFailedStepHook(),
],
(World world) => app.main(),
);
}

View File

@ -37,7 +37,8 @@ void main() {
TakeScreenshot(),
// overrides
TapWidgetWithType(),
TapFirstWidget(),
TapWidgetWithLabel(),
ExpectWidgetWithText(),
WaitUntilTypeExists(),
ExpectTextToBePresent(),
ExpectWidgetWithTextWithin(),

View File

@ -1,265 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'gherkin_suite_test.dart';
// **************************************************************************
// GherkinSuiteTestGenerator
// **************************************************************************
class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
_CustomGherkinIntegrationTestRunner(
TestConfiguration configuration,
Future<void> Function(World) appMainFunction,
) : super(configuration, appMainFunction);
@override
void onRun() {
testFeature2();
}
void testFeature2() {
runFeature(
'Sending and receiving chat messages:',
<String>['@env:aliceandbob1'],
() {
runScenario(
'Bob receives the message from Alice',
<String>['@env:aliceandbob1'],
(TestDependencies dependencies) async {
await runStep(
'Given I wait until the widget with type "ProfileRow" is present',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait for 4 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait until the text "Contact is offline, messages can\'t be delivered right now" is absent',
<String>[],
null,
dependencies,
);
await runStep(
'When I fill the "txtCompose" field with "hello! this is a test!"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the "btnSend" button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect a "MessageBubble" widget with text "hello! this is a test!\u202F" to be present within 5 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect a "MessageBubble" widget with text "hello! this is a test!\u202F" to be present within 5 seconds',
<String>[],
null,
dependencies,
);
},
onBefore: () async => onBeforeRunFeature(
'Sending and receiving chat messages',
<String>['@env:aliceandbob1'],
),
onAfter: null,
);
runScenario(
'Bob replies to a message from Alice',
<String>['@env:aliceandbob1'],
(TestDependencies dependencies) async {
await runStep(
'Given I wait until the widget with type "ProfileRow" is present',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait for 4 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait until the text "Contact is offline, messages can\'t be delivered right now" is absent',
<String>[],
null,
dependencies,
);
await runStep(
'When I fill the "txtCompose" field with "hello! this is a test!"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the "btnSend" button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect a "MessageBubble" widget with text "hello! this is a test!\u202F" to be present within 5 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button with tooltip "Reply to this message"',
<String>[],
null,
dependencies,
);
await runStep(
'And I fill the "txtCompose" field with "yay the test worked"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the "btnSend" button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect to see the message "yay the test worked\u202F" replying to "hello! this is a test!" within 5 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'And I take a screenshot',
<String>[],
null,
dependencies,
);
},
onBefore: null,
onAfter: () async => onAfterRunFeature(
'Sending and receiving chat messages',
),
);
},
);
}
}
void executeTestSuite(
TestConfiguration configuration,
Future<void> Function(World) appMainFunction,
) {
_CustomGherkinIntegrationTestRunner(configuration, appMainFunction).run();
}

View File

@ -1,20 +0,0 @@
var reporter = require('cucumber-html-reporter');
var options = {
theme: 'bootstrap',
jsonFile: 'gherkin/reports/integration_response_data.json',
output: 'gherkin/reports/index.html',
reportSuiteAsScenarios: true,
scenarioTimestamp: true,
launchReport: true,
metadata: {
"App Version":"0.3.2",
"Test Environment": "STAGING",
"Browser": "Chrome 54.0.2840.98",
"Platform": "Windows 10",
"Parallel": "Scenarios",
"Executed": "Remote"
}
};
reporter.generate(options);

View File

@ -27,14 +27,46 @@ StepDefinitionGeneric TapWidgetWithType() {
);
}
StepDefinitionGeneric TapFirstWidget() {
StepDefinitionGeneric TapWidgetWithLabel() {
return given2<String, String, FlutterWorld>(
RegExp(r'I tap the {string} widget with label {string}$'),
(ofType, text, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(widgetTypeByName(ofType), FindType.type),
context.world.appDriver.findBy(text, FindType.text),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.tap(finder);
await context.world.appDriver.waitForAppToSettle();
},
);
}
StepDefinitionGeneric ExpectWidgetWithText() {
return given2<String, String, FlutterWorld>(
RegExp(r'I expect a {string} widget with text {string}$'),
(ofType, text, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(widgetTypeByName(ofType), FindType.type),
context.world.appDriver.findBy(text, FindType.text),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.isPresent(finder);
await context.world.appDriver.waitForAppToSettle();
},
);
}
StepDefinitionGeneric TapButtonWithText() {
return given1<String, FlutterWorld>(
RegExp(r'I tap the first {string} (?:button|element|label|icon|field|text|widget)$'),
RegExp(r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'),
(input1, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(Flwtch, FindType.type),
context.world.appDriver.findBy(input1, FindType.key),
firstMatchOnly: true);
context.world.appDriver.findBy(Flwtch, FindType.type),
context.world.appDriver.findBy(input1, FindType.key),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.tap(finder);
@ -195,6 +227,10 @@ Type widgetTypeByName(String input1) {
return ProfileRow;
case "TorIcon":
return TorIcon;
case "button":
return ElevatedButton;
case "ProfileRow":
return ProfileRow;
default:
throw("Unknown type $input1. add to integration_test/features/overrides.dart");
}

View File

@ -51,14 +51,21 @@ class Flwtch extends StatefulWidget {
}
}
class FlwtchState extends State<Flwtch> with WindowListener {
class FlwtchState extends State<Flwtch> with WindowListener, WidgetsBindingObserver {
final TextStyle biggerFont = const TextStyle(fontSize: 18);
late Cwtch cwtch;
late ProfileListState profs;
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
final MethodChannel shutdownLinuxMethodChannel = MethodChannel('im.cwtch.linux.shutdown');
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
Future<dynamic> shutdownDirect(MethodCall call) {
print(call);
cwtch.Shutdown();
return Future.value({});
}
@override
initState() {
print("initState: running...");
@ -69,6 +76,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
profs = ProfileListState();
notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
shutdownMethodChannel.setMethodCallHandler(modalShutdown);
shutdownLinuxMethodChannel.setMethodCallHandler(shutdownDirect);
print("initState: creating cwtchnotifier, ffi");
if (Platform.isAndroid) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList);
@ -178,6 +186,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
// coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
Future<void> _externalNotificationClicked(MethodCall call) async {
var args = jsonDecode(call.arguments);
var profile = profs.getProfile(args["ProfileOnion"])!;
var convo = profile.contactList.getContact(args["Handle"])!;
Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
@ -222,6 +231,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
globalAppState.focus = false;
}
@override
void dispose() {
cwtch.Shutdown();

View File

@ -54,7 +54,7 @@ class WindowsNotificationManager implements NotificationsManager {
}
// clicked
if (event is ToastActivated) {
active = false;
active = false;
}
// if a supplied action was clicked
if (event is ToastInteracted) {

View File

@ -104,6 +104,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchTextField(
key: Key("displayNameFormElement"),
controller: ctrlrNick,
autofocus: false,
hintText: AppLocalizations.of(context)!.yourDisplayName,
@ -146,6 +147,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Checkbox(
key: Key("passwordCheckBox"),
value: usePassword,
fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor),
activeColor: theme.current().defaultButtonActiveColor,
@ -179,6 +181,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchPasswordField(
key: Key("currentPasswordFormElement"),
controller: ctrlrOldPass,
autoFillHints: [AutofillHints.newPassword],
validator: (value) {
@ -204,6 +207,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchPasswordField(
key: Key("passwordFormElement"),
controller: ctrlrPass,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
@ -224,6 +228,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchPasswordField(
key: Key("confirmPasswordFormElement"),
controller: ctrlrPass2,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation

View File

@ -179,13 +179,14 @@ class _MessageViewState extends State<MessageView> {
// size because of the additional wrapping end encoding
// hybrid groups should allow these numbers to be the same.
static const P2PMessageLengthMax = 7000;
static const GroupMessageLengthMax = 1800;
static const GroupMessageLengthMax = 1600;
void _sendMessage([String? ignoredParam]) {
var isGroup = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(Provider.of<AppState>(context, listen: false).selectedConversation!)!.isGroup;
// peers and groups currently have different length constraints (servers can store less)...
var lengthOk = (isGroup && ctrlrCompose.value.text.length < GroupMessageLengthMax) || ctrlrCompose.value.text.length <= P2PMessageLengthMax;
var actualMessageLength = ctrlrCompose.value.text.length;
var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax;
if (ctrlrCompose.value.text.isNotEmpty && lengthOk) {
if (Provider.of<AppState>(context, listen: false).selectedConversation != null && Provider.of<AppState>(context, listen: false).selectedIndex != null) {
@ -250,6 +251,10 @@ class _MessageViewState extends State<MessageView> {
bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false;
bool isGroup = Provider.of<ContactInfoState>(context).isGroup;
var charLength = ctrlrCompose.value.text.characters.length;
var expectedLength = ctrlrCompose.value.text.length;
var numberOfBytesMoreThanChar = (expectedLength - charLength);
var composeBox = Container(
color: Provider.of<Settings>(context).theme.backgroundMainColor,
padding: EdgeInsets.all(2),
@ -274,11 +279,16 @@ class _MessageViewState extends State<MessageView> {
keyboardType: TextInputType.multiline,
enableIMEPersonalizedLearning: false,
minLines: 1,
maxLength: isGroup ? GroupMessageLengthMax : P2PMessageLengthMax,
maxLength: (isGroup ? GroupMessageLengthMax : P2PMessageLengthMax) - numberOfBytesMoreThanChar,
maxLengthEnforcement: MaxLengthEnforcement.enforced,
maxLines: null,
onFieldSubmitted: _sendMessage,
enabled: !isOffline,
onChanged: (String x) {
setState(() {
// we need to force a rerender here to update the max length count
});
},
decoration: InputDecoration(
hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage,
hintStyle: TextStyle(color: Provider.of<Settings>(context).theme.sendHintTextColor),

View File

@ -9,20 +9,26 @@ const hints = [AutofillHints.password];
// Provides a styled Password Input Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
class CwtchPasswordField extends StatefulWidget {
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = false, this.autoFillHints = hints});
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = false, this.autoFillHints = hints, this.key});
final TextEditingController controller;
final FormFieldValidator validator;
final Function(String)? action;
final bool autofocus;
final Iterable<String> autoFillHints;
final Key? key;
@override
_CwtchTextFieldState createState() => _CwtchTextFieldState();
_CwtchPasswordTextFieldState createState() => _CwtchPasswordTextFieldState();
}
class _CwtchTextFieldState extends State<CwtchPasswordField> {
class _CwtchPasswordTextFieldState extends State<CwtchPasswordField> {
bool obscureText = true;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
// todo: translations
@ -42,7 +48,6 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
autofillHints: widget.autoFillHints,
autovalidateMode: AutovalidateMode.always,
onFieldSubmitted: widget.action,
textInputAction: TextInputAction.unspecified,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(

View File

@ -8,7 +8,7 @@ doNothing(String x) {}
// Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
class CwtchTextField extends StatefulWidget {
CwtchTextField({required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false});
CwtchTextField({required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false, this.key});
final TextEditingController controller;
final String hintText;
final FormFieldValidator? validator;
@ -16,6 +16,7 @@ class CwtchTextField extends StatefulWidget {
final bool autofocus;
final bool multiLine;
final bool number;
final Key? key;
@override
_CwtchTextFieldState createState() => _CwtchTextFieldState();

View File

@ -21,6 +21,8 @@ struct _MyApplication {
char** dart_entrypoint_arguments;
};
FlMethodChannel* channel;
// Redefining from flutter/engine::shell/platform/linux/fl_dart_project.cc
// struct def required here to enable compiler to allow access to variables
struct _FlDartProject {
@ -35,6 +37,19 @@ struct _FlDartProject {
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
#include <dlfcn.h>
gboolean
on_shutdown(GtkWidget *widget, GdkEvent *event, gpointer data)
{
fl_method_channel_invoke_method(channel, "onWindowClose",
nullptr, nullptr, nullptr, nullptr);
return TRUE;
}
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
@ -72,6 +87,9 @@ static void my_application_activate(GApplication* application) {
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_signal_connect(G_OBJECT(window),
"destroy", G_CALLBACK(on_shutdown), NULL);
g_autoptr(FlDartProject) project = fl_dart_project_new();
// Check if assets folder is relative to the executable or if we can use a system copy
@ -113,11 +131,22 @@ static void my_application_activate(GApplication* application) {
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
// Create a specific channel for shutting down cwtch when the close button is triggered
// We have registered the "destroy" handle above for this reason
FlEngine *engine = fl_view_get_engine(view);
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine);
channel =
fl_method_channel_new(messenger,
"im.cwtch.linux.shutdown", FL_METHOD_CODEC(codec));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) {
MyApplication* self = MY_APPLICATION(application);

View File

@ -1,10 +1,9 @@
#!/bin/bash
pkill tor
paths=$(find . -wholename "./integration_test/features/05*/*.feature" | sort | sed -z "s/\\n/','/g;s/,'$//;s/^/'/")
paths=$(find . -wholename "./integration_test/features/*/$1*.feature" | sort | sed -z "s/\\n/','/g;s/,'$//;s/^/'/")
sed "s|featurePaths: REPLACED_BY_SCRIPT|featurePaths: <String>[$paths]|" integration_test/gherkin_suite_test.editable.dart > integration_test/gherkin_suite_test.dart
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
LD_LIBRARY_PATH=/home/erinn/Android/Goprojects/libcwtch-go/ CWTCH_HOME=./integration_test/env/temp/ flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
LD_LIBRARY_PATH=./linux/ CWTCH_HOME=./integration_test/env/temp/ flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
node index2.js
xdg-open integration_test/gherkin/reports/cucumber_report.html