Skip to content

Commit

Permalink
Fix: configureWebView gets called multiple times
Browse files Browse the repository at this point in the history
fix #31
  • Loading branch information
fjcaetano committed Mar 12, 2018
1 parent 8160d36 commit 4b814c5
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 13 deletions.
101 changes: 89 additions & 12 deletions Example/ReCaptcha_Tests/Core/DispatchQueue__Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class DispatchQueue__Tests: XCTestCase {
super.tearDown()
}

// MARK: Throttle

func test__Throttle_Nil_Context() {
// Execute closure called once
let exp0 = expectation(description: "did call single closure")
Expand All @@ -31,14 +33,15 @@ class DispatchQueue__Tests: XCTestCase {
waitForExpectations(timeout: 1)

// Does not execute first closure
let exp1 = expectation(description: "")
let exp1 = expectation(description: "did call last closure")
DispatchQueue.main.throttle(deadline: .now() + 0.1) {
XCTFail("Shouldn't be called")
}

DispatchQueue.main.throttle(deadline: .now() + 0.1) {
exp1.fulfill()
}
DispatchQueue.main.throttle(
deadline: .now() + 0.1,
action: exp1.fulfill
)

waitForExpectations(timeout: 1)
}
Expand All @@ -48,9 +51,11 @@ class DispatchQueue__Tests: XCTestCase {
let exp0 = expectation(description: "did call single closure")
let c0 = UUID()

DispatchQueue.main.throttle(deadline: .now() + 0.1, context: c0) {
exp0.fulfill()
}
DispatchQueue.main.throttle(
deadline: .now() + 0.1,
context: c0,
action: exp0.fulfill
)

waitForExpectations(timeout: 1)

Expand All @@ -61,17 +66,89 @@ class DispatchQueue__Tests: XCTestCase {
XCTFail("Shouldn't be called")
}

DispatchQueue.main.throttle(deadline: .now() + 0.1, context: c1) {
exp1.fulfill()
}
DispatchQueue.main.throttle(
deadline: .now() + 0.1,
context: c1,
action: exp1.fulfill
)

// Execute in a different context
let exp2 = expectation(description: "execute on different context")
let c2 = UUID()
DispatchQueue.main.throttle(deadline: .now() + 0.1, context: c2) {
exp2.fulfill()
DispatchQueue.main.throttle(
deadline: .now() + 0.1,
context: c2,
action: exp2.fulfill
)

waitForExpectations(timeout: 1)
}

// MARK: Debounce

func test__Debounce_Nil_Context() {
// Does not execute sequenced closures
let exp0 = expectation(description: "did call first closure")

DispatchQueue.main.debounce(
interval: 0.1,
action: exp0.fulfill
)

DispatchQueue.main.debounce(interval: 0) {
XCTFail("Shouldn't be called")
}

waitForExpectations(timeout: 1)

// Executes closure after previous has timed out
let exp1 = expectation(description: "did call closure")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
DispatchQueue.main.debounce(
interval: 0.1,
action: exp1.fulfill
)
}

waitForExpectations(timeout: 3)
}

func test__Debounce_Context() {
// Does not execute sequenced closures
let exp0 = expectation(description: "did call first closure")
let c0 = UUID()

DispatchQueue.main.debounce(
interval: 0.1,
context: c0,
action: exp0.fulfill
)

DispatchQueue.main.debounce(interval: 0, context: c0) {
XCTFail("Shouldn't be called")
}

// Execute in a different context
let c1 = UUID()
let exp1 = expectation(description: "executes in different context")
DispatchQueue.main.debounce(
interval: 0,
context: c1,
action: exp1.fulfill
)

waitForExpectations(timeout: 1)

// Executes closure after previous has timed out
let exp2 = expectation(description: "did call closure")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
DispatchQueue.main.debounce(
interval: 0.1,
context: c0,
action: exp2.fulfill
)
}

waitForExpectations(timeout: 5)
}
}
28 changes: 28 additions & 0 deletions Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,34 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
waitForExpectations(timeout: 10)
}

func test__Configure_Web_View__Called_Once() {
var count = 0
let exp0 = expectation(description: "configure webview")

// Configure WebView
let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
manager.configureWebView { _ in
if count < 3 {
manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) }
}

count += 1
exp0.fulfill()
}

manager.validate(on: presenterView) { _ in
XCTFail("should not call completion")
}

waitForExpectations(timeout: 10)

let exp1 = expectation(description: "waiting for extra calls")
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: exp1.fulfill)
waitForExpectations(timeout: 2)

XCTAssertEqual(count, 1)
}

// MARK: Stop

func test__Stop() {
Expand Down
26 changes: 26 additions & 0 deletions ReCaptcha/Classes/DispatchQueue+Throttle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ extension DispatchQueue {
/// Stores a throttle DispatchWorkItem instance for a given context
private static var workItems = [AnyHashable: DispatchWorkItem]()

/// Stores the last call times for a given context
private static var lastDebounceCallTimes = [AnyHashable: DispatchTime]()

/// An object representing a context if none is given
private static let nilContext = UUID()

Expand All @@ -35,4 +38,27 @@ extension DispatchQueue {
DispatchQueue.workItems[context]?.cancel()
DispatchQueue.workItems[context] = worker
}

/**
- parameters:
- interval: The interval in which new calls will be ignored
- context: The context in which the debounce should be executed
- action: The closure to be executed
Executes a closure and ensures no other executions will be made during the interval.
*/
func debounce(interval: Double, context: AnyHashable = nilContext, action: @escaping () -> Void) {
let now = DispatchTime.now()
if let last = DispatchQueue.lastDebounceCallTimes[context], last + interval > now {
return
}

DispatchQueue.lastDebounceCallTimes[context] = now + interval
async(execute: action)

// Cleanup & release context
throttle(deadline: now + interval) {
DispatchQueue.lastDebounceCallTimes.removeValue(forKey: context)
}
}
}
6 changes: 5 additions & 1 deletion ReCaptcha/Classes/ReCaptchaWebViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,11 @@ fileprivate extension ReCaptchaWebViewManager {
}

case .showReCaptcha:
configureWebView?(webView)
// Ensures `configureWebView` won't get called multiple times in a short period
DispatchQueue.main.debounce(interval: 1) { [weak self] in
guard let `self` = self else { return }
self.configureWebView?(self.webView)
}

case .didLoad:
// For testing purposes
Expand Down

0 comments on commit 4b814c5

Please sign in to comment.