Merge branch 'gherkin' of git.openprivacy.ca:cwtch.im/cwtch-ui into gherkin
This commit is contained in:
commit
6844118d0f
|
@ -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
|
@ -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(),
|
||||
);
|
||||
}
|
|
@ -37,7 +37,8 @@ void main() {
|
|||
TakeScreenshot(),
|
||||
// overrides
|
||||
TapWidgetWithType(),
|
||||
TapFirstWidget(),
|
||||
TapWidgetWithLabel(),
|
||||
ExpectWidgetWithText(),
|
||||
WaitUntilTypeExists(),
|
||||
ExpectTextToBePresent(),
|
||||
ExpectWidgetWithTextWithin(),
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue