newbee here on QML. Apologies if the question is too basic but I am stuck.
I am trying something very simple, running a simple QML unit test, but it needs to grab the image of the window and saved to the file system.
So I was looking at this tutorial and the following docs:
https://www.youtube.com/watch?v=8EUjXQzFfoA
https://doc.qt.io/qt-6/qml-qttest-testcase.html
and tried to combine both as such:
Item {
width: 800
height: 600
MyWindow
{
id: myWindowTest
}
TestCase{
name: "TestButtonClick"
when: windowShown
function test_ClickButton()
{
var notSavedText = "Not saved"
var savedText = "Saved!"
myWindowTest.grabToImage(function(result) {
console.log(result)
result.saveToFile("something.png");
});
console.log("step 1")
verify(myWindowTest.saveStatus === notSavedText, "Save status failed!")
console.log("sending click")
mouseClick(myWindowTest.saveButton)
console.log("step 2")
verify(myWindowTest.saveStatus === savedText, "Save status failed!")
}
}
}
yet the ".png" is never generated. See the output of "qmltestrunner.exe"
any clues what I don't get the image, notice that the logs are never called so it failed, but the ids look correct to me.
thank you very much!
In case you don't insist on using your call-method - the given method in docs works just fine for me:
var image = grabImage(rect);
try {
compare(image.width, 100);
} catch (ex) {
image.save("debug.png");
throw ex;
}
Which would result in this for you:
Item {
width: 800
height: 600
MyWindow
{
id: myWindowTest
}
TestCase{
name: "TestButtonClick"
when: windowShown
function test_ClickButton()
{
var notSavedText = "Not saved"
var savedText = "Saved!"
var image = grabImage(myWindowTest);
image.save("something.png");
console.log("step 1")
verify(myWindowTest.saveStatus === notSavedText, "Save status failed!")
console.log("sending click")
mouseClick(myWindowTest.saveButton)
console.log("step 2")
verify(myWindowTest.saveStatus === savedText, "Save status failed!")
}
}
}
thank you all for the leads, indeed the "waitForRendering" resolved the issue
function test_ClickButton()
{
var notSavedText = "Not saved"
var savedText = "Saved!"
console.log("step 1")
verify(myWindowTest.saveStatus === notSavedText, "Save status failed!")
console.log("sending click")
mouseClick(myWindowTest.saveButton)
console.log("step 2")
verify(myWindowTest.saveStatus === savedText, "Save status failed!")
myWindowTest.grabToImage(function(result) {
console.log(result)
result.saveToFile("scrshot.png");
});
**waitForRendering(myWindowTest, 100)**
}
Related
I have a button ("Next Word") that when pressed shows a new view ("PracticeResult"). The idea is that everytime you press it, a new random word appears on the screen. But so far, I have to press the button twice to get it to show the next image because it's triggered with a boolean state.
I've tried changing the State back to false with an ".onAppear" toggle but it doesn't work. I've also tried using an origin State to toggle the variable back to false but it hasn't worked either. I'm quite new to SwiftUI so any tips would be appreciated! Thanks in advance.
struct PracticeView: View {
#State var isTapped: Bool = false
var body: some View {
NavigationView {
ZStack {
Color.white
VStack() {
Image("lightlogolong")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300.0, height: 100.0)
.cornerRadius(100)
.animation(.easeIn, value: 10)
NavigationLink(destination:
ContentView().navigationBarBackButtonHidden(true) {
HomeButton()
}
Group {
if (isTapped == true){
PracticeResult()
}
}.onAppear{
isTapped.toggle()
}
Button("Next Word", action:{
self.isTapped.toggle()
})
.padding()
.frame(width: 150.0, height: 40.0)
.font(.title2)
.accentColor(.black)
.background(Color("appblue"))
.clipShape(Capsule())
}
}
}
}
}
You could try this simple approach, using a UUID or something like it,
to trigger your PracticeResult view every time you tap the button. Note in particular the PracticeResult().id(isTapped)
struct PracticeView: View {
#State var isTapped: UUID? // <--- here
var body: some View {
NavigationView {
ZStack {
Color.white
VStack() {
Image("lightlogolong")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300.0, height: 100.0)
.cornerRadius(100)
.animation(.easeIn, value: 10)
NavigationLink(destination:
ContentView().navigationBarBackButtonHidden(true) {
HomeButton()
}
if isTapped != nil {
PracticeResult().id(isTapped) // <--- here
}
Button("Next Word", action:{
isTapped = UUID() // <--- here
})
.padding()
.frame(width: 150.0, height: 40.0)
.font(.title2)
.accentColor(.black)
.background(Color.blue)
.clipShape(Capsule())
}
}
}
}
}
// for testing
struct PracticeResult: View {
#State var randomWord: String = UUID().uuidString
var body: some View {
Text(randomWord)
}
}
if you want your button trigger a new random word appears on the screen, you should use button to change that random word
for example (you can try it on Preview):
struct PracticeView: View {
let words = ["one","two","three"]
#State var wordToDisplay: String?
var body: some View {
VStack {
if let wordToDisplay = wordToDisplay {
Text(wordToDisplay)
}
Spacer()
Button("Next Word") {
wordToDisplay = words.filter { $0 != wordToDisplay }.randomElement()
}
}.frame(height: 50)
}
}
I have a chatView with a list of chatRow Views (messages)
each chatView has a snapshot listener with firebase, so I should get real time updates if I add a new message to the conversation
The problem I have is: when I add a new message my chatView shows ALL the messages I added before plus the new message, PLUS the same list again....if I add another message then the list repeats again
I assume I need to drop/refresh the previous views shown in the Foreach loop...how can I drop/refresh the view so it can receive refreshed NON repeated data?
struct ChatView: View {
#EnvironmentObject var chatModel: ChatsViewModel
let chat: Conversation
let user = UserService.shared.user
#State var messagesSnapshot = [Message]()
#State var newMessageInput = ""
var body: some View {
NavigationView {
VStack {
ScrollViewReader { scrollView in
ScrollView {
ForEach(chat.messages, id: \.id) { message in
if user.name == message.createdBy {
ChatRow(message: message, isMe: true)
} else {
ChatRow(message: message, isMe: false)
}
}
.onAppear(perform: {scrollView.scrollTo(chat.messages.count-1)})
}
}
Spacer()
//send a new message
ZStack {
Rectangle()
.foregroundColor(.white)
RoundedRectangle(cornerRadius: 20)
.stroke(Color("LightGrayColor"), lineWidth: 2)
.padding()
HStack {
TextField("New message...", text: $newMessageInput, onCommit: {
print("Send Message")
})
.padding(30)
Button(action: {
chatModel.sendMessageChat(newMessageInput, in: chat, chatid: chat.id ?? "")
print("Send message.")
}) {
Image(systemName: "paperplane")
.imageScale(.large)
.padding(30)
}
}
}
.frame(height: 70)
}
.navigationTitle("Chat")
}
}
}
function to add message to the conversation
func addMessagesToConv(conversation: Conversation, index: Int) {
var mensajesTotal = [Message]()
let ref = self.db.collection("conversations").document(conversation.id!).collection("messages")
.order(by: "date")
.addSnapshotListener { querySnapshotmsg, error in
if error == nil {
//loop throug the messages/docs
for msgDoc in querySnapshotmsg!.documents {
var m = Message() //emtpy struc message
m.createdBy = msgDoc["created_by"] as? String ?? ""
m.date = msgDoc["date"] as? Timestamp ?? Timestamp()
m.msg = msgDoc["msg"] as? String ?? ""
m.id = msgDoc.documentID //firebase auto id
mensajesTotal.append(m) //append this message to the total of messages
self.chats[index].messages.removeAll()
self.chats[index].messages = mensajesTotal
}
} else {
print("error: \(error!.localizedDescription)")
}
}
}
You've defined mensajesTotal outside of your snapshot listener. So, it's getting appended to every time.
To fix this, move this line:
var mensajesTotal = [Message]()
to inside the addSnapshotListener closure.
You have two options:
Clear mensajesTotal each time you get an update from the database, as #jnpdx's answer shows.
Process the more granular updates in querySnapshotmsg.documentChanges to perform increment updates in your UI, as also shown in the documentation on detecting changes between snapshots.
There is no difference in the data transferred between client and server between these approaches, so use whatever is easiest (that'd typically be #1) or most efficient on the UI (that's usually #2).
I implemented a React native Toolbar its showing me all actions which i have given but after press on that any action it gives me error. Its entering in the function that onActionSelected But after that my Logout() , Move() any function is not working.
where i am going wrong plz help
code:
<ToolbarAndroid
title="Shopcon"
style={styles.toolbar}
actions={toolbarActions}
onActionSelected={this.onActionSelected}
/>
const toolbarActions = [
{title: 'Logout', show: 'never'},
{title: 'Got to Login', show: 'never'},
];
onActionSelected(position) {
if (position === 0) {
console.log("I am in 0");
this.Logout();
}
if (position === 1) {
console.log("I am in 1");
this.Move();
}
}
async Logout() {
console.log("fun1");
const { navigate } = this.props.navigation;
try {
await AsyncStorage.removeItem(STORAGE_KEY);
Alert.alert("Logout Success! Token:" + DEMO_TOKEN)
} catch (error) {
console.log('AsyncStorage error: ' + error.message);
}
navigate("Login");
}
Move(){
console.log("fun2")
const { navigate } = this.props.navigation;
navigate("Login");
}
both are entering in onActionSelected(position) function but not entering in those other functions.
where i am going wrong please help.
I want to alter how an asyncCommand is being hit (currently from a button), so I would need to access the asyncCommand from code. I don't want to have to alter what this asyncCommand is doing, it is dealing with payment details.
I have tried Googling but I cant find anything, I am also new to KO.
This is what I'm trying to achieve:
Click on a button (a separate button with its own asyncCommand method
which checks a flag) The 'execute' will do the following:
If (flag) - show modal
modal has two options - Continue / Cancel
If continue - hit asyncCommand command for original button (card payment one).
If cancel - go back to form
If (!flag)
Hit asyncCommand command for original button (card payment one).
Can this be done?
Thanks in advance for any help.
Clare
This is what I have tried:
FIRST BUTTON
model.checkAddress = ko.asyncCommand({
execute: function (complete)
{
makePayment.execute();
if (data.shippingOutOfArea === true || (data.shippingOutOfArea === null && data.billingOutOfArea === true)) {
model.OutOfArea.show(true);
}
complete();
},
canExecute: function (isExecuting) {
return !isExecuting;
}
});
ORIGINAL BUTTON
model.makePayment = ko.asyncCommand({
execute: function (complete) {
}})
MODAL
model.OutOfArea = {
header: ko.observable("Out of area"),
template: "modalOutOfArea",
closeLabel: "Close",
primaryLabel: "Continue",
cancelLabel: "Change Address",
show: ko.observable(false), /* Set to true to show initially */
sending: ko.observable(false),
onClose: function ()
{
model.EditEmailModel.show(false);
},
onAction: function () {
makePayment.execute();
},
onCancel: function ()
{
model.EditEmailModel.show(false);
}
};
You will have two async commands actually for this scenario. One to open up the modal and another one for the modal.
Eg:
showPaymentPromptCmd = ko.asyncCommand({
execute: function(complete) {
if (modalRequired) {
showModal();
} else {
makePayement();
}
complete();
},
canExecute: function(isExecuting) {
return !isExecuting;
}
});
//Called by Continue button on your modal.
makePaymentCmd = ko.asyncCommand({
execute: function(complete) {
makePayement();
complete();
},
canExecute: function(isExecuting) {
return !isExecuting;
}
});
var
function makePayement() {
//some logic
}
I'm running into an issue with Meteor subscription not setting checkbox "checked" after refresh. Basically, if I have Meteor running and change the JS, the app works as expected (pulls data from Meteor.user() and sets checkbox accordingly). However, if I refresh the app all my checkboxes are set to false. I don't see why that should happen, any ideas?
My subscription looks as follows:
Server-side
Meteor.publish("user-preferences", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'notification': 1}});
} else {
this.ready();
}
});
Client-side:
Meteor.subscribe("user-preferences", function() {
if (Meteor.user()) {
var notification = Meteor.user().notification;
// Check to see if notification and systems exists; if not, default to TRUE
if (notification) {
Session.set('aNotificationPreference', notification.hasOwnProperty('a') ? notification.a : true);
}
else {
Session.set('aNotificationPreference', true);
}
}
else {
// TRUE too if no user yet
Session.set('aNotificationPreference', true);
}
});
This is the helper that will look up the session variable to make things reactive:
Template.preferences.helpers({
isChecked: function() {
console.log('#### PREF INITIAL: ' + Session.get("aNotificationPreference"));
var pref = Session.get("aNotificationPreference");
if (typeof pref === 'undefined') {
Meteor.setTimeout(function() {
pref = Session.get("aNotificationPreference");
console.log('#### PREF IN TIMEOUT: ' + pref);
return pref ? 'checked' : false;
}, 1250);
}
else {
console.log('#### PREF in ELSE: ' + pref);
return pref ? 'checked' : false;
}
}
});
Finally, this is the HTML checkbox field:
<input type="checkbox" data-role="flipswitch" data-theme="b" name="aNotificationSwitch" id="aNotificationSwitch" data-mini="true" checked={{isChecked}}>
This is based of the Blaze documentation on this specifically and other posts I found.
I know the issue is in the helper but I'm not sure how to address it. The logs on failure show as follows:
Application Cache Checking event (index):1
Application Cache NoUpdate event (index):1
#### PREF INITIAL: undefined index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:63
Connected to Meteor Server. index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:37
#### PREF INITIAL: true index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:63
#### PREF in ELSE: true index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:73
Connected to Meteor Server. index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:37
#### PREF IN TIMEOUT: true
I am not an expert in Meteor yet.
But I would change the JavaScript you have for:
if(Meteor.isClient){
Template.preferences.helpers({
isChecked: function() { return Meteor.user().notification ? 'checked' : false; }
});
Template.preferences.events({
'click #aNotificationSwitch': function(event){
var checked = event.target.checked; // true or false
Meteor.users.update({_id: Meteor.userId()}, $set: {notification: checked});
}
});
}
It's different from your approach though. With this way you don't need to use session variables.
Allowing to update users:
Meteor.users.allow({
update: function(userId, user){
return user._id == userId;
}
});
The best solution I found was to set the flipswitch with jQueryMobile by doing the following on my panel's open event in the Template.body.events:
'panelbeforeopen #mypanel': function() {
//Refresh flipswitches prior to opening sidepanel otherwise they default to disabled
$("#aNotificationSwitch").flipswitch("enable").flipswitch("refresh");
$("#bNotificationSwitch").flipswitch("enable").flipswitch("refresh");
$("#cNotificationSwitch").flipswitch("enable").flipswitch("refresh");
}