I am trying to run unit test case for timer as defined below where timer is initialised in CPTimer
var searchTimer: TimerType?
func initialTimer() {
searchTimer = CPTimer(timeInterval: 2.0, repeats: false) { [weak self] in
self?.notifySearchResult(for: self?.searchQuery ?? "")
}
}
Unit test case using Quick & Nimble
context("to set search timer") {
beforeEach {
timerStub = CPTimerStub(
timeInterval: 2.0,
repeats: false) {
// Don't need to use in testing
}
subject.searchTimer = timerStub
}
it("check search timer is set") {
subject.initialTimer()
waitUntil(timeout: DispatchTimeInterval.milliseconds(3_000)) { done in
expect(subject.searchController.searchResultsController)
.to(beAKindOf(NCDeviceSearchResultController.self))
done()
}
expect(subject.searchTimer).toNot(beNil())
expect(timerStub.cpTimeInterval) == 2
expect(timerStub.cpRepeats) == false
expect(timerStub.cpBlock).toNot(beNil())
}
}
But always getting failed to cover as sowing red
Related
I am trying to convert some code from objective-c to swift and along with that use async/await as much as possible.
This code leverages a C library that uses callbacks with a void* context. There are parts of our code where we need to "wait" until the callback is completed. This can often be 2 different callbacks for success or failure.
Below is an example of the code
class Test
{
var sessionStarted: Bool = false
let sessionSemaphore = DispatchSemaphore(value: 0)
// We need the session to be available once this function returns
func startSession() throws
{
guard !sessionStarted else {
print("Session already started")
return
}
let callback: KMSessionEventCallback = { inSession, inEvent, inContext in
guard let session = inSession, let context = inContext else { return }
let testContext: Test = Unmanaged<Test>.fromOpaque(context).takeUnretainedValue()
testContext.handleSessionUpdate(session: session, event: inEvent)
}
let result = KMSessionAttach("SessionTest", &callback, Unmanaged.passUnretained(self).toOpaque())
if result != KM_SUCCESS {
print("Failed to attach session")
}
// Currently uses a semaphore to wait. =(
let dispatchResult = self.sessionSemaphore.wait(timeout: .now() + DispatchTimeInterval.seconds(30))
if dispatchResult == .timedOut {
print("Waited 30 seconds and never attached session")
}
}
private func handleSessionUpdate(session: KMSession, event: KMSessionEvent)
{
switch event
{
case KM_SESSION_ATTACHED:
self.sessionStarted = true
case KM_SESSION_TERMINATED:
fallthrough
case KM_SESSION_FAILED:
fallthrough
case KM_SESSION_DETACHED:
self.sessionStarted = false
default:
print("Unknown State")
}
self.sessionSemaphore.signal()
}
}
I am struggling to figure out how to use continuations here because I cannot capture any context within these C callbacks.
I'm trying to make Clock(actual time) / Timer (CountDown) to my app.. I know my solution is kind of rough, but everything works kind of ok BUT. When I pause my Countdown and then start it again - In the first time Counter adds time (for like a second) that passed since i press Pause button and afterwards loads my timeCounter value that i saved and continues counting down.. Why? Also if you have better smoother solution I will be very pleased.. THANKS!
import SwiftUI
struct Clock : View {
#State private var nowDate: Date = Date()
#State private var referenceDate: Date = Date()
#State private var display: Bool = true
#State private var updateTimer: Timer?
#State private var timeCounter: Double = 0
#State private var timerRunning: Bool = false
var body: some View {
VStack {
Button(action: {
self.display.toggle()
}) {
Text(display ? "COUNTDOWN" : "TIME")
}
Text(display ? "\(timeString(date: nowDate))" : "\(countDownString(for: referenceDate))")
HStack {
Button(action: {
self.display = false
self.timeCounter = 10
self.referenceDate = Date(timeIntervalSinceNow: self.timeCounter)
}) {
Text("10")
}
}
Button(action: {
if self.timerRunning {
self.referenceDate = Date(timeIntervalSinceNow: self.timeCounter)
self.startTimer()
self.timerRunning.toggle()
} else {
self.updateTimer?.invalidate()
self.timerRunning.toggle()
}
}) {
Text(timerRunning ? "START" : "PAUSE")
}
}
.onAppear {
self.startTimer()
}
}
//MARK: Timer
func startTimer() {
self.updateTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.nowDate = Date()
if self.referenceDate.distance(to: self.nowDate) >= 0 {
self.referenceDate = Date(timeIntervalSinceNow: 0)
self.display = true
}
self.timeCounter -= 1
}
}
//MARK: - CountDown Timer
func countDownString(for date: Date) -> String {
let calendar = Calendar(identifier: .gregorian)
let components = calendar
.dateComponents([.hour, .minute, .second],
from: nowDate,
to: referenceDate)
return String(format: "%02d:%02d:%02d",
components.hour ?? 00,
components.minute ?? 00,
components.second ?? 00)
}
//MARK: - Clock Timer
var timeFormat: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter
}
func timeString(date: Date) -> String {
let time = timeFormat.string(from: date)
return time
}
}
Ive seen lots of complicated ways to do this on the net.
New in iOS 14:
let closeDate = Date(timeIntervalSinceNow: 60.0)
Text(closeDate, style: .relative)
maybe you need Timer publisher
https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-a-timer-with-swiftui
Hi I have a timer running which is like it should show a component for 30sec after every 10 seconds. My code is like this`
import { timer } from "rxjs";
componentWillReceiveProps(nextProps, nextState) {
console.log("RECEIVED PROPS");
if (this.props.venuesBonusOfferDuration === 0) {
this.subscribe.unsubscribe();
}
this.firstTimerSubscription;
this.secondTimerSubscription;
// if (this.state.isMounted) {
if (
nextProps.showBonusObj &&
(!nextProps.exitVenue.exitVenueSuccess || nextProps.enterVenues)
) {
// console.log("isMounted" + this.state.isMounted);
//if (this.state.isMounted) {
let milliseconds = nextProps.venuesBonusOfferDuration * 1000;
this.source = timer(milliseconds);
this.firstTimerSubscription = this.source.subscribe(val => {
console.log("hiding bonus offer");
this.firstTimerSubscription.unsubscribe();
this.props.hideBonusOffer();
this.secondTimerSubscription = bonusApiTimer.subscribe(val => {
console.log("caling timer" + val);
this.props.getVenuesBonusOffer(
this.props.venues.venues.id,
this.props.uid,
this.props.accessToken
);
});
});
//}
} else {
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimer UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimer UNSUBSCRIBED");
}
} catch (error) {
console.log(
"error when removing bonusoffer timer" + JSON.stringify(error)
);
}
//}
}
}
`
Problem is if I try to unsubscribe this * this.firstTimerSubscription* and this.secondTimerSubscription like this
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimerunmount UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimerunmount UNSUBSCRIBED");
}
} catch (error) {
console.log("error bonusoffer timer" + JSON.stringify(error));
}
its still prints logs within timer like "hiding bonus offer" and "calling timer".
Can someone please point out the issue. It been a day since am into this.
Any help is appreciated.
The problem is that you subscribe multiple times (whenever component receives props) and reassign newest subscription to firstTimerSubscription or secondTimerSubscription references. But doing that, subscriptions does not magically vanish. To see how it works here is a demo:
const source = timer(1000, 1000);
let subscribe;
subscribe = source.subscribe(val => console.log(val));
subscribe = source.subscribe(val => console.log(val));
setTimeout(() => {
subscribe.unsubscribe();
}, 2000)
Even though you unsubscribed, the first subscription keeps emiting. And the problem is that you lost a reference to it, so you can't unsubscribe now.
Easy fix could be to check whether you already subscribed and unsubscribe if so, before subscribing:
this.firstTimerSubscription ? this.firstTimerSubscription.unsubscribe: false;
this.firstTimerSubscription = this.source.subscribe(...
I wouldn't use a second timer. Just do a interval of 10 seconds. The interval emits the iteration number 1, 2, 3..... You can use the modulo operator on that tick. Following example code (for example with 1 second interval) prints true and false in console. After true it needs 3 seconds to show false. After false it needs 1 second to show true.
interval(1000).pipe(
map(tick => tick % 4 !== 0),
distinctUntilChanged(),
).subscribe(x => console.log(x));
I have the following code (a Parse Server query into a function that gets fired every 20 seconds and checks if there are new rows in the Messages class (table)):
/* Variables */
var messagesArray = [PFObject]()
var theMessages = [PFObject]()
/* func queryMessages() code */
let messId1 = "\(currentUser.objectId!)\(userObj.objectId!)"
let messId2 = "\(userObj.objectId!)\(currentUser.objectId!)"
let predicate = NSPredicate(format:"messageID = '\(messId1)' OR messageID = '\(messId2)'")
let query = PFQuery(className: MESSAGES_CLASS_NAME, predicate: predicate)
query.whereKey(MESSAGES_DELETED_BY, notContainedIn: [currentUser.objectId!])
query.order(byAscending: "createdAt")
query.skip = skip
query.findObjectsInBackground { (objects, error)-> Void in
if error == nil {
for i in 0..<objects!.count { self.messagesArray.append(objects![i]) }
if (objects!.count == 100) {
self.skip = self.skip + 100
self.queryMessages()
} else {
self.messagesArray = objects!
/*test*/
let messDiff = self.messagesArray.count - self.theMessages.count
print("MESSAGES ARRAY: \(self.messagesArray.count)")
print("THE MESSAGES: \(self.theMessages.count)")
print("MESS DIFF: \(messDiff)\n")
if self.theMessages.count < self.messagesArray.count {
for i in 0..<messDiff {
self.theMessages.append(self.messagesArray[i])
}// ./ For
self.messagesTableView.reloadData()
}// ./ If
// Scroll TableView down to the bottom
if objects!.count != 0 {
Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(self.scrollTableViewToBottom), userInfo: nil, repeats: false)
}
}// ./ If
// error
} else { self.simpleAlert("\(error!.localizedDescription)")
}}
So, the app is running on my device, and with another device I send 2 new messages to the test User on my device. After 20 seconds, a Timer calls the function above and I obviously get this in the console:
MESSAGES ARRAY: 9
THE MESSAGES: 7
MESS DIFF: 2
So, what I would need is to simply append the 2 new items of the messagesArray into the theMessages array, so that when my TableView will reload, it'll just add 2 new rows on its bottom.
Obviously now I get the first 2 messages again because of this code:
for i in 0..<messDiff {
self.theMessages.append(self.messagesArray[i])
}// ./ For
I cannot figure out what 'workaround' I should use to achieve what I need...
As you are reloading the entire table view without animations anyway why not simply
self.theMessages = self.messagesArray
self.messagesTableView.reloadData()
Or if the new messages are reliably at the end of the array get them with suffix and append them to theMessages
let newMessages = Array(self.messagesArray.suffix(messDiff))
self.theMessages.append(contentsOf: newMessages)
if self.theMessages.count < self.messagesArray.count {
let temp = self.theMessages.count
for i in 0..<messDiff {
self.theMessages.append(self.messagesArray[i+temp])
}// ./ For
self.messagesTableView.reloadData()}
change your code reload data code to this
If i do:
scope.$watch('obj', function(newObj, oldObj) {
//...
}, true);
How do I find the key-value pair in the object that changed?
.
Only to understand what I try to do:
I have an object of the form:
scope.actions = {
action1: false,
action2: false
}
When the boolean changes, I want to assign function calls to it. Something like, DO-action - UNDO-action.
So I watch it the following way:
scope.$watch('actions', function(newObj, oldObj) {
/*PSEUDO CODE START*/
IF (action1 changed && action1 true) {
do-func1();
}
IF (action1 changed && action1 false) {
undo-func1();
}
...
/*PSEUDO CODE END*/
}, true);
My problem here is, that if I check the values for their boolean, all the functions get called. So the point here is, how do I find the changed key-value pair in the object?
scope.$watch('actions', function(newActions, oldActions) {
for(var i in newActions) {
if(newActions.hasOwnProperty(i)) {
var newAction = newActions[i];
var oldAction = oldActions[i];
if(newAction !== oldAction) {
if(newAction === true) {
doActions[i](); // do-func-i();
} else if (newAction === false) {
undoActions[i](); // undo-func-i();
}
}
}
}
}, true);
doActions here is a conventional map of actions
doActions = {
action1 : function() {
console.log('action1');
},
action2 : function() {
console.log('action2');
}
}
doActions['action1'] will reference first function
You may have an array as well, but then you'll need to introduce an index to fetch proper function doActions[0]