Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions Sources/ToggleManager/ToggleManager+Overrides.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,24 @@ extension ToggleManager {
queue.sync(flags: .barrier) {
guard let mutableValueProvider = mutableValueProvider else { return [] }
let variables = mutableValueProvider.variables
log("Deleting all overrides.")
mutableValueProvider.deleteAll()

// Clear cache for all variables that had overrides
for variable in variables {
cache[variable] = nil
}

// Send updated values to existing subjects
for variable in variables {
DispatchQueue.main.async {
self.subjectsRefs[variable]?.send(completion: .finished)
self.subjectsRefs[variable] = nil
if let subject = self.subjectsRefs[variable] {
let newValue = self.value(for: variable)
subject.send(newValue)
}
}
}
log("Deleting all overrides.")
mutableValueProvider.deleteAll()

hasOverrides = false
return variables
}
Expand Down
7 changes: 5 additions & 2 deletions Sources/ToggleManager/ToggleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,11 @@ extension ToggleManager {
self.cache[variable] = nil
mutableValueProvider.delete(variable)
DispatchQueue.main.async {
self.subjectsRefs[variable]?.send(completion: .finished)
self.subjectsRefs[variable] = nil
// Send the new default value to the subject
if let subject = self.subjectsRefs[variable] {
let newValue = self.value(for: variable)
subject.send(newValue)
}
self.hasOverrides = !mutableValueProvider.variables.isEmpty
}
}
Expand Down
120 changes: 117 additions & 3 deletions Tests/Suites/ToggleManager/ToggleManager+PublishingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@

import XCTest
import Combine
import Toggles
@testable import Toggles

class ToggleManager_PublishingTests: XCTestCase {

private var cancellables: Set<AnyCancellable> = []
private let datasourceUrl = Bundle.toggles.url(forResource: "TestDatasource", withExtension: "json")!

override func tearDown() {
cancellables.removeAll()
super.tearDown()
}

func test_publishers() throws {
let url = Bundle.toggles.url(forResource: "TestDatasource", withExtension: "json")!
let inMemoryProvider = InMemoryValueProvider()
let manager = try! ToggleManager(mutableValueProvider: inMemoryProvider, datasourceUrl: url)
let manager = try ToggleManager(mutableValueProvider: inMemoryProvider, datasourceUrl: datasourceUrl)

let valueExpectation = self.expectation(description: #function)
let cachedPublisherValueExpectation = self.expectation(description: #function)
Expand Down Expand Up @@ -55,4 +60,113 @@ class ToggleManager_PublishingTests: XCTestCase {

wait(for: [valueExpectation, cachedPublisherValueExpectation], timeout: 5.0)
}

func test_publisherRemainsActiveAfterRemoveOverrides() throws {
let manager = try ToggleManager(mutableValueProvider: InMemoryValueProvider(), datasourceUrl: datasourceUrl)
let variable = "integer_toggle"

var receivedValues: [Value] = []
let expectation = XCTestExpectation(description: "Receive updated values")
expectation.expectedFulfillmentCount = 3

manager.publisher(for: variable)
.sink { value in
receivedValues.append(value)
expectation.fulfill()
}
.store(in: &cancellables)

manager.set(.int(999), for: variable)
manager.removeOverrides()
manager.reactToConfigurationChanges()

wait(for: [expectation], timeout: 1.0)

XCTAssertEqual(receivedValues.count, 3)
XCTAssertEqual(receivedValues[0], .int(42))
XCTAssertEqual(receivedValues[1], .int(999))
XCTAssertEqual(receivedValues[2], .int(42))
}

func test_publisherRemainsActiveAfterDelete() throws {
let manager = try ToggleManager(mutableValueProvider: InMemoryValueProvider(), datasourceUrl: datasourceUrl)
let variable = "string_toggle"

var receivedValues: [Value] = []
let expectation = XCTestExpectation(description: "Receive updated values")
expectation.expectedFulfillmentCount = 3

manager.publisher(for: variable)
.sink { value in
receivedValues.append(value)
expectation.fulfill()
}
.store(in: &cancellables)

manager.set(.string("overridden"), for: variable)
manager.delete(variable)
manager.reactToConfigurationChanges()

wait(for: [expectation], timeout: 1.0)

XCTAssertEqual(receivedValues.count, 3)
XCTAssertEqual(receivedValues[0], .string("Hello World"))
XCTAssertEqual(receivedValues[1], .string("overridden"))
XCTAssertEqual(receivedValues[2], .string("Hello World"))
}

func test_subjectReferencesPreservedAfterRemoveOverrides() throws {
let manager = try ToggleManager(mutableValueProvider: InMemoryValueProvider(), datasourceUrl: datasourceUrl)
let variable = "integer_toggle"

_ = manager.publisher(for: variable)
XCTAssertNotNil(manager.subjectsRefs[variable])

manager.set(.int(999), for: variable)
manager.removeOverrides()

XCTAssertNotNil(manager.subjectsRefs[variable], "Subject should still exist after removeOverrides()")
}

func test_subjectReferencesPreservedAfterDelete() throws {
let manager = try ToggleManager(mutableValueProvider: InMemoryValueProvider(), datasourceUrl: datasourceUrl)
let variable = "integer_toggle"

_ = manager.publisher(for: variable)
XCTAssertNotNil(manager.subjectsRefs[variable])

manager.set(.int(888), for: variable)
manager.delete(variable)

XCTAssertNotNil(manager.subjectsRefs[variable], "Subject should still exist after delete()")
}

func test_toggleObservableRemainsActiveAfterRemoveOverrides() throws {
let manager = try ToggleManager(mutableValueProvider: InMemoryValueProvider(), datasourceUrl: datasourceUrl)
let variable = "boolean_toggle"

let observable = ToggleObservable(manager: manager, variable: variable)

var receivedValues: [Bool?] = []
let expectation = XCTestExpectation(description: "Receive updated bool values")
expectation.expectedFulfillmentCount = 3

observable.$boolValue
.sink { boolValue in
receivedValues.append(boolValue)
expectation.fulfill()
}
.store(in: &cancellables)

manager.set(.bool(false), for: variable)
manager.removeOverrides()
manager.reactToConfigurationChanges()

wait(for: [expectation], timeout: 1.0)

XCTAssertEqual(receivedValues.count, 3)
XCTAssertEqual(receivedValues[0], true)
XCTAssertEqual(receivedValues[1], false)
XCTAssertEqual(receivedValues[2], true)
}
}
2 changes: 2 additions & 0 deletions TogglesDemo/TogglesDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -408,6 +409,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading