Add a `Defaults.Serializable` protocol (#57)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
parent
f64b3ca562
commit
534157f1b5
|
@ -5,9 +5,20 @@ on:
|
|||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
scheme: [Defaults-macOS, Defaults-iOS, Defaults-tvOS]
|
||||
include:
|
||||
- scheme: Defaults-macOS
|
||||
destination: macOS
|
||||
- scheme: Defaults-iOS
|
||||
destination: iOS Simulator,name=iPhone 8
|
||||
- scheme: Defaults-tvOS
|
||||
destination: tvOS Simulator,name=Apple TV
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: swift test
|
||||
- name: Run tests
|
||||
run: xcodebuild clean test -project Defaults.xcodeproj -scheme ${{ matrix.scheme }} -destination "platform=${{ matrix.destination }}" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
|
@ -8,6 +8,79 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
52D6D9871BEFF229002C0205 /* Defaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Defaults.framework */; };
|
||||
71056FF425DE6DEF00524EDA /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
|
||||
71056FF525DE6DEF00524EDA /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
|
||||
71056FF625DE6DEF00524EDA /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
|
||||
7108EAC125942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */; };
|
||||
7108EAC225942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */; };
|
||||
7108EAC325942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */; };
|
||||
7108EAF425949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */; };
|
||||
7108EAF525949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */; };
|
||||
7108EAF625949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */; };
|
||||
71362AC025A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */; };
|
||||
71362AC125A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */; };
|
||||
71362AC225A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */; };
|
||||
71362ACB25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */; };
|
||||
71362ACC25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */; };
|
||||
71362ACD25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */; };
|
||||
7150D6F425C2968700201966 /* DefaultsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */; };
|
||||
7150D6F525C2968700201966 /* DefaultsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */; };
|
||||
7150D6F625C2968700201966 /* DefaultsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */; };
|
||||
7152A04D25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */; };
|
||||
7152A04E25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */; };
|
||||
7152A04F25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */; };
|
||||
71573E9925A445CE00F18D4E /* DefaultsCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */; };
|
||||
71573EA125A445D400F18D4E /* DefaultsCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */; };
|
||||
71573EA925A445DB00F18D4E /* DefaultsCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */; };
|
||||
7168638325E886DB00F55131 /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
|
||||
718B783325917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
|
||||
718B783425917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
|
||||
718B783525917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
|
||||
718B783625917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
|
||||
718B783F25917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
|
||||
718B784025917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
|
||||
718B784125917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
|
||||
718B784225917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
|
||||
7191AD872591977700AD472F /* DefaultsArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191AD862591977700AD472F /* DefaultsArrayTests.swift */; };
|
||||
7191AD882591977700AD472F /* DefaultsArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191AD862591977700AD472F /* DefaultsArrayTests.swift */; };
|
||||
7191AD892591977700AD472F /* DefaultsArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191AD862591977700AD472F /* DefaultsArrayTests.swift */; };
|
||||
719F5E20258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
|
||||
719F5E21258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
|
||||
719F5E22258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
|
||||
719F5E23258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
|
||||
71A7132D260497EE004095EE /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
|
||||
71A7132E260497EE004095EE /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
|
||||
71A7132F260497EE004095EE /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
|
||||
71A71330260497EE004095EE /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
|
||||
71A713482604A084004095EE /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
|
||||
71A713492604A084004095EE /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
|
||||
71A7134A2604A084004095EE /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
|
||||
71A7134B2604A084004095EE /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
|
||||
71A7135B2604A13B004095EE /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
|
||||
71A7135C2604A13B004095EE /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
|
||||
71A7135D2604A13B004095EE /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
|
||||
71A7135E2604A13B004095EE /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
|
||||
71B96F1E259986F100079F69 /* DefaultsCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */; };
|
||||
71B96F1F259986F100079F69 /* DefaultsCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */; };
|
||||
71B96F20259986F100079F69 /* DefaultsCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */; };
|
||||
71C55FF6259C13190053CCB3 /* DefaultsNSColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C55FF4259C13190053CCB3 /* DefaultsNSColorTests.swift */; };
|
||||
71C56007259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */; };
|
||||
71C56009259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */; };
|
||||
71C5602E259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */; };
|
||||
71C5602F259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */; };
|
||||
71C56030259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */; };
|
||||
71F002F925959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */; };
|
||||
71F002FA25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */; };
|
||||
71F002FB25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */; };
|
||||
71F003042595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */; };
|
||||
71F003052595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */; };
|
||||
71F003062595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */; };
|
||||
71F0030F2595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */; };
|
||||
71F003102595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */; };
|
||||
71F003112595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */; };
|
||||
71F0031A2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */; };
|
||||
71F0031B2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */; };
|
||||
71F0031C2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */; };
|
||||
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
|
||||
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
|
||||
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
|
||||
|
@ -74,6 +147,29 @@
|
|||
52D6D9F01BEFFFBE002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
52D6DA0F1BF000BD002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6614F6E222FC6E1C00B0C9CE /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = readme.md; sourceTree = "<group>"; usesTabs = 1; };
|
||||
71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Migration+Protocol.swift"; sourceTree = "<group>"; };
|
||||
71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Migration+Extensions.swift"; sourceTree = "<group>"; };
|
||||
71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Migration+UserDefaults.swift"; sourceTree = "<group>"; };
|
||||
7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSwiftUITests.swift; sourceTree = "<group>"; };
|
||||
7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsDictionaryTests.swift; sourceTree = "<group>"; };
|
||||
71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCollectionCustomElementTests.swift; sourceTree = "<group>"; };
|
||||
71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSetAlgebraTests.swift; sourceTree = "<group>"; };
|
||||
7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsMigrationTests.swift; sourceTree = "<group>"; };
|
||||
7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSetAlgebraCustomElementTests.swift; sourceTree = "<group>"; };
|
||||
71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCollectionTests.swift; sourceTree = "<group>"; };
|
||||
7168638225E886DB00F55131 /* Migration+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Migration+Defaults.swift"; sourceTree = "<group>"; };
|
||||
718B783225917CCA004FF90D /* Defaults+Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Defaults+Protocol.swift"; sourceTree = "<group>"; };
|
||||
718B783E25917D09004FF90D /* Defaults+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Defaults+Extensions.swift"; sourceTree = "<group>"; };
|
||||
7191AD862591977700AD472F /* DefaultsArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsArrayTests.swift; sourceTree = "<group>"; };
|
||||
719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Defaults+Bridge.swift"; sourceTree = "<group>"; };
|
||||
71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCodableTests.swift; sourceTree = "<group>"; };
|
||||
71C55FF4259C13190053CCB3 /* DefaultsNSColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsNSColorTests.swift; sourceTree = "<group>"; };
|
||||
71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsUIColorTests.swift; sourceTree = "<group>"; };
|
||||
71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSetTests.swift; sourceTree = "<group>"; };
|
||||
71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsNSSecureCodingTests.swift; sourceTree = "<group>"; };
|
||||
71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCustomBridgeTests.swift; sourceTree = "<group>"; };
|
||||
71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsEnumTests.swift; sourceTree = "<group>"; };
|
||||
71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCodableEnumTests.swift; sourceTree = "<group>"; };
|
||||
8933C7841EB5B820000D00A4 /* Defaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Defaults.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
8933C7891EB5B82A000D00A4 /* DefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DefaultsTests.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
AD2FAA261CD0B6D800659CF4 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; };
|
||||
|
@ -179,6 +275,25 @@
|
|||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
71056FF025DE6DEF00524EDA /* Migration */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7156A8F226205E0C00A1A66E /* v5 */,
|
||||
7168638225E886DB00F55131 /* Migration+Defaults.swift */,
|
||||
);
|
||||
path = Migration;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7156A8F226205E0C00A1A66E /* v5 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */,
|
||||
71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */,
|
||||
71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */,
|
||||
);
|
||||
path = v5;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8933C7811EB5B7E0000D00A4 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -191,6 +306,22 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
8933C7891EB5B82A000D00A4 /* DefaultsTests.swift */,
|
||||
7191AD862591977700AD472F /* DefaultsArrayTests.swift */,
|
||||
7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */,
|
||||
7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */,
|
||||
71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */,
|
||||
71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */,
|
||||
71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */,
|
||||
71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */,
|
||||
71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */,
|
||||
71C55FF4259C13190053CCB3 /* DefaultsNSColorTests.swift */,
|
||||
71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */,
|
||||
71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */,
|
||||
71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */,
|
||||
71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */,
|
||||
71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */,
|
||||
7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */,
|
||||
7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */,
|
||||
);
|
||||
name = Tests;
|
||||
path = Tests/DefaultsTests;
|
||||
|
@ -215,6 +346,7 @@
|
|||
E30E93D822E9425E00530C8F /* Defaults */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
71056FF025DE6DEF00524EDA /* Migration */,
|
||||
8933C7841EB5B820000D00A4 /* Defaults.swift */,
|
||||
E339B3B72449F10D00E7A40A /* UserDefaults.swift */,
|
||||
E339B3B22449ED2000E7A40A /* Reset.swift */,
|
||||
|
@ -222,6 +354,9 @@
|
|||
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
|
||||
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */,
|
||||
E3EB3E32216505920033B089 /* Utilities.swift */,
|
||||
718B783225917CCA004FF90D /* Defaults+Protocol.swift */,
|
||||
718B783E25917D09004FF90D /* Defaults+Extensions.swift */,
|
||||
719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */,
|
||||
);
|
||||
path = Defaults;
|
||||
sourceTree = "<group>";
|
||||
|
@ -264,6 +399,7 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Defaults-iOS" */;
|
||||
buildPhases = (
|
||||
71A71338260497FF004095EE /* SwiftLint */,
|
||||
52D6D9771BEFF229002C0205 /* Sources */,
|
||||
52D6D9781BEFF229002C0205 /* Frameworks */,
|
||||
52D6D9791BEFF229002C0205 /* Headers */,
|
||||
|
@ -300,6 +436,7 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "Defaults-watchOS" */;
|
||||
buildPhases = (
|
||||
71A713532604A090004095EE /* SwiftLint */,
|
||||
52D6D9DD1BEFFF6E002C0205 /* Sources */,
|
||||
52D6D9DE1BEFFF6E002C0205 /* Frameworks */,
|
||||
52D6D9DF1BEFFF6E002C0205 /* Headers */,
|
||||
|
@ -318,6 +455,7 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "Defaults-tvOS" */;
|
||||
buildPhases = (
|
||||
71A713472604A05A004095EE /* SwiftLint */,
|
||||
52D6D9EB1BEFFFBE002C0205 /* Sources */,
|
||||
52D6D9EC1BEFFFBE002C0205 /* Frameworks */,
|
||||
52D6D9ED1BEFFFBE002C0205 /* Headers */,
|
||||
|
@ -365,6 +503,8 @@
|
|||
DD7502811C68FCFC006590AF /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Defaults-macOS Tests";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "Defaults-OS Tests";
|
||||
productReference = DD75027A1C68FCFC006590AF /* Defaults-macOS Tests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
|
@ -436,6 +576,8 @@
|
|||
Base,
|
||||
);
|
||||
mainGroup = 52D6D9721BEFF229002C0205;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 52D6D97D1BEFF229002C0205 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
|
@ -504,6 +646,60 @@
|
|||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
71A71338260497FF004095EE /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "swiftlint\n";
|
||||
};
|
||||
71A713472604A05A004095EE /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "swiftlint\n";
|
||||
};
|
||||
71A713532604A090004095EE /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "swiftlint\n";
|
||||
};
|
||||
E3FD0A4B25BDA35F0011D293 /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -529,13 +725,20 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
71A7132D260497EE004095EE /* Migration+Protocol.swift in Sources */,
|
||||
71A7132E260497EE004095EE /* Migration+Extensions.swift in Sources */,
|
||||
71A7132F260497EE004095EE /* Migration+UserDefaults.swift in Sources */,
|
||||
71A71330260497EE004095EE /* Migration+Defaults.swift in Sources */,
|
||||
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */,
|
||||
E38C9F28244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
719F5E21258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
|
||||
E3EB3E33216505920033B089 /* Utilities.swift in Sources */,
|
||||
718B784025917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
718B783425917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -543,7 +746,22 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7152A04D25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */,
|
||||
71573E9925A445CE00F18D4E /* DefaultsCollectionTests.swift in Sources */,
|
||||
71362ACB25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */,
|
||||
71F002F925959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */,
|
||||
7108EAC125942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */,
|
||||
71F0031A2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */,
|
||||
71C56007259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */,
|
||||
71C5602E259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */,
|
||||
7108EAF425949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */,
|
||||
71F003042595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */,
|
||||
8933C7901EB5B82D000D00A4 /* DefaultsTests.swift in Sources */,
|
||||
71B96F1E259986F100079F69 /* DefaultsCodableTests.swift in Sources */,
|
||||
7150D6F425C2968700201966 /* DefaultsMigrationTests.swift in Sources */,
|
||||
71F0030F2595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */,
|
||||
7191AD872591977700AD472F /* DefaultsArrayTests.swift in Sources */,
|
||||
71362AC025A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -551,13 +769,20 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
71A7135B2604A13B004095EE /* Migration+Protocol.swift in Sources */,
|
||||
71A7135C2604A13B004095EE /* Migration+Extensions.swift in Sources */,
|
||||
71A7135D2604A13B004095EE /* Migration+UserDefaults.swift in Sources */,
|
||||
71A7135E2604A13B004095EE /* Migration+Defaults.swift in Sources */,
|
||||
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
|
||||
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||
E3EB3E3A216507C40033B089 /* Utilities.swift in Sources */,
|
||||
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
719F5E23258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
|
||||
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
718B784225917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||
E339B3B62449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
718B783625917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -565,13 +790,20 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
71A713482604A084004095EE /* Migration+Protocol.swift in Sources */,
|
||||
71A713492604A084004095EE /* Migration+Extensions.swift in Sources */,
|
||||
71A7134A2604A084004095EE /* Migration+UserDefaults.swift in Sources */,
|
||||
71A7134B2604A084004095EE /* Migration+Defaults.swift in Sources */,
|
||||
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
|
||||
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||
E3EB3E3B216507C40033B089 /* Utilities.swift in Sources */,
|
||||
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
719F5E22258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
|
||||
8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
718B784125917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||
E339B3B52449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
718B783525917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -581,11 +813,18 @@
|
|||
files = (
|
||||
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
|
||||
E38C9F27244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||
7168638325E886DB00F55131 /* Migration+Defaults.swift in Sources */,
|
||||
E3EB3E39216507C30033B089 /* Utilities.swift in Sources */,
|
||||
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||
719F5E20258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||
E3EB3E36216507B50033B089 /* Observation.swift in Sources */,
|
||||
71056FF425DE6DEF00524EDA /* Migration+Protocol.swift in Sources */,
|
||||
71056FF525DE6DEF00524EDA /* Migration+Extensions.swift in Sources */,
|
||||
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||
718B783F25917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||
E339B3B32449ED2000E7A40A /* Reset.swift in Sources */,
|
||||
71056FF625DE6DEF00524EDA /* Migration+UserDefaults.swift in Sources */,
|
||||
718B783325917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -593,7 +832,22 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7152A04E25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */,
|
||||
71573EA125A445D400F18D4E /* DefaultsCollectionTests.swift in Sources */,
|
||||
71362ACC25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */,
|
||||
71C55FF6259C13190053CCB3 /* DefaultsNSColorTests.swift in Sources */,
|
||||
71F002FA25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */,
|
||||
7108EAC225942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */,
|
||||
71F0031B2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */,
|
||||
71C5602F259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */,
|
||||
7108EAF525949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */,
|
||||
71F003052595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */,
|
||||
8933C78F1EB5B82C000D00A4 /* DefaultsTests.swift in Sources */,
|
||||
71B96F1F259986F100079F69 /* DefaultsCodableTests.swift in Sources */,
|
||||
7150D6F525C2968700201966 /* DefaultsMigrationTests.swift in Sources */,
|
||||
71F003102595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */,
|
||||
7191AD882591977700AD472F /* DefaultsArrayTests.swift in Sources */,
|
||||
71362AC125A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -601,7 +855,22 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7152A04F25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */,
|
||||
71573EA925A445DB00F18D4E /* DefaultsCollectionTests.swift in Sources */,
|
||||
71362ACD25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */,
|
||||
71F002FB25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */,
|
||||
7108EAC325942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */,
|
||||
71F0031C2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */,
|
||||
71C56009259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */,
|
||||
71C56030259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */,
|
||||
7108EAF625949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */,
|
||||
71F003062595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */,
|
||||
8933C78E1EB5B82C000D00A4 /* DefaultsTests.swift in Sources */,
|
||||
71B96F20259986F100079F69 /* DefaultsCodableTests.swift in Sources */,
|
||||
7150D6F625C2968700201966 /* DefaultsMigrationTests.swift in Sources */,
|
||||
71F003112595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */,
|
||||
7191AD892591977700AD472F /* DefaultsArrayTests.swift in Sources */,
|
||||
71362AC225A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
@ -39,7 +40,6 @@
|
|||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
extension Defaults.CodableBridge {
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
// Some codable values like URL and enum are encoded as a top-level
|
||||
// string which JSON can't handle, so we need to wrap it in an array
|
||||
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750
|
||||
let data = try JSONEncoder().encode([value])
|
||||
return String(String(data: data, encoding: .utf8)!.dropFirst().dropLast())
|
||||
} catch {
|
||||
print(error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard let jsonString = object else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return [Value].init(jsonString: "[\(jsonString)]")?.first
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Any `Value` which protocol conforms to `Codable` and `Defaults.Serializable` will use `CodableBridge`
|
||||
to do the serialization and deserialization.
|
||||
*/
|
||||
extension Defaults {
|
||||
public struct TopLevelCodableBridge<Value: Codable>: CodableBridge {}
|
||||
}
|
||||
|
||||
/**
|
||||
`RawRepresentableCodableBridge` is indeed because if `enum SomeEnum: String, Codable, Defaults.Serializable`
|
||||
the compiler will confuse between `RawRepresentableBridge` and `TopLevelCodableBridge`.
|
||||
*/
|
||||
extension Defaults {
|
||||
public struct RawRepresentableCodableBridge<Value: RawRepresentable & Codable>: CodableBridge {}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct URLBridge: CodableBridge {
|
||||
public typealias Value = URL
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct RawRepresentableBridge<Value: RawRepresentable>: Defaults.Bridge {
|
||||
public typealias Value = Value
|
||||
public typealias Serializable = Value.RawValue
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
value?.rawValue
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard let rawValue = object else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value(rawValue: rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct NSSecureCodingBridge<Value: NSSecureCoding>: Defaults.Bridge {
|
||||
public typealias Value = Value
|
||||
public typealias Serializable = Data
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let object = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version below macOS 10.13 and iOS 11.0 does not support `archivedData(withRootObject:requiringSecureCoding:)`.
|
||||
// We need to set `requiresSecureCoding` by ourself.
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
|
||||
return try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true)
|
||||
} else {
|
||||
let keyedArchiver = NSKeyedArchiver()
|
||||
keyedArchiver.requiresSecureCoding = true
|
||||
keyedArchiver.encode(object, forKey: NSKeyedArchiveRootObjectKey)
|
||||
return keyedArchiver.encodedData
|
||||
}
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard let data = object else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Value
|
||||
} catch {
|
||||
print(error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct OptionalBridge<Wrapped: Defaults.Serializable>: Defaults.Bridge {
|
||||
public typealias Value = Wrapped.Value
|
||||
public typealias Serializable = Wrapped.Serializable
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
Wrapped.bridge.serialize(value)
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
Wrapped.bridge.deserialize(object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct ArrayBridge<Element: Defaults.Serializable>: Defaults.Bridge {
|
||||
public typealias Value = [Element]
|
||||
public typealias Serializable = [Element.Serializable]
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let array = value as? [Element.Value] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return array.map { Element.bridge.serialize($0) }.compact()
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard let array = object else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return array.map { Element.bridge.deserialize($0) }.compact() as? Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct DictionaryBridge<Key: LosslessStringConvertible & Hashable, Element: Defaults.Serializable>: Defaults.Bridge {
|
||||
public typealias Value = [Key: Element.Value]
|
||||
public typealias Serializable = [String: Element.Serializable]
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let dictionary = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// `Key` which stored in `UserDefaults` have to be `String`
|
||||
return dictionary.reduce(into: Serializable()) { memo, tuple in
|
||||
memo[String(tuple.key)] = Element.bridge.serialize(tuple.value)
|
||||
}
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard let dictionary = object else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return dictionary.reduce(into: Value()) { memo, tuple in
|
||||
// Use `LosslessStringConvertible` to create `Key` instance
|
||||
guard let key = Key(tuple.key) else {
|
||||
return
|
||||
}
|
||||
|
||||
memo[key] = Element.bridge.deserialize(tuple.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
We need both `SetBridge` and `SetAlgebraBridge`.
|
||||
|
||||
Because `Set` conforms to `Sequence` but `SetAlgebra` not.
|
||||
|
||||
Set conforms to `Sequence`, so we can convert it into an array with `Array.init<S>(S)` and store it in the `UserDefaults`.
|
||||
|
||||
But `SetAlgebra` does not, so it is hard to convert it into an array.
|
||||
|
||||
Thats why we need `Defaults.SetAlgebraSerializable` protocol to convert it into an array.
|
||||
*/
|
||||
extension Defaults {
|
||||
public struct SetBridge<Element: Defaults.Serializable & Hashable>: Defaults.Bridge {
|
||||
public typealias Value = Set<Element>
|
||||
public typealias Serializable = Any
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let set = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if Element.isNativelySupportedType {
|
||||
return Array(set)
|
||||
}
|
||||
|
||||
return set.map { Element.bridge.serialize($0 as? Element.Value) }.compact()
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
if Element.isNativelySupportedType {
|
||||
guard let array = object as? [Element] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Set(array)
|
||||
}
|
||||
|
||||
guard
|
||||
let array = object as? [Element.Serializable],
|
||||
let elements = array.map({ Element.bridge.deserialize($0) }).compact() as? [Element]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Set(elements)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct SetAlgebraBridge<Value: Defaults.SetAlgebraSerializable>: Defaults.Bridge where Value.Element: Defaults.Serializable {
|
||||
public typealias Value = Value
|
||||
public typealias Element = Value.Element
|
||||
public typealias Serializable = Any
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let setAlgebra = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if Element.isNativelySupportedType {
|
||||
return setAlgebra.toArray()
|
||||
}
|
||||
|
||||
return setAlgebra.toArray().map { Element.bridge.serialize($0 as? Element.Value) }.compact()
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
if Element.isNativelySupportedType {
|
||||
guard let array = object as? [Element] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value(array)
|
||||
}
|
||||
|
||||
guard
|
||||
let array = object as? [Element.Serializable],
|
||||
let elements = array.map({ Element.bridge.deserialize($0) }).compact() as? [Element]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value(elements)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
public struct CollectionBridge<Value: Defaults.CollectionSerializable>: Defaults.Bridge where Value.Element: Defaults.Serializable {
|
||||
public typealias Value = Value
|
||||
public typealias Element = Value.Element
|
||||
public typealias Serializable = Any
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let collection = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if Element.isNativelySupportedType {
|
||||
return Array(collection)
|
||||
}
|
||||
|
||||
return collection.map { Element.bridge.serialize($0 as? Element.Value) }.compact()
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
if Element.isNativelySupportedType {
|
||||
guard let array = object as? [Element] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value(array)
|
||||
}
|
||||
|
||||
guard
|
||||
let array = object as? [Element.Serializable],
|
||||
let elements = array.map({ Element.bridge.deserialize($0) }).compact() as? [Element]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value(elements)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import Foundation
|
||||
import CoreGraphics
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
extension Defaults.Serializable {
|
||||
public static var isNativelySupportedType: Bool { false }
|
||||
}
|
||||
|
||||
extension Data: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Date: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Bool: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Int: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension UInt: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Double: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Float: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension String: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension CGFloat: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Int8: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension UInt8: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Int16: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension UInt16: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Int32: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension UInt32: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension Int64: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension UInt64: Defaults.Serializable {
|
||||
public static let isNativelySupportedType = true
|
||||
}
|
||||
|
||||
extension URL: Defaults.Serializable {
|
||||
public static let bridge = Defaults.URLBridge()
|
||||
}
|
||||
|
||||
extension Defaults.Serializable where Self: Codable {
|
||||
public static var bridge: Defaults.TopLevelCodableBridge<Self> { Defaults.TopLevelCodableBridge() }
|
||||
}
|
||||
|
||||
extension Defaults.Serializable where Self: RawRepresentable {
|
||||
public static var bridge: Defaults.RawRepresentableBridge<Self> { Defaults.RawRepresentableBridge() }
|
||||
}
|
||||
|
||||
extension Defaults.Serializable where Self: RawRepresentable & Codable {
|
||||
public static var bridge: Defaults.RawRepresentableCodableBridge<Self> { Defaults.RawRepresentableCodableBridge() }
|
||||
}
|
||||
|
||||
extension Defaults.Serializable where Self: NSSecureCoding {
|
||||
public static var bridge: Defaults.NSSecureCodingBridge<Self> { Defaults.NSSecureCodingBridge() }
|
||||
}
|
||||
|
||||
extension Optional: Defaults.Serializable where Wrapped: Defaults.Serializable {
|
||||
public static var isNativelySupportedType: Bool { Wrapped.isNativelySupportedType }
|
||||
public static var bridge: Defaults.OptionalBridge<Wrapped> { Defaults.OptionalBridge() }
|
||||
}
|
||||
|
||||
extension Defaults.CollectionSerializable where Element: Defaults.Serializable {
|
||||
public static var bridge: Defaults.CollectionBridge<Self> { Defaults.CollectionBridge() }
|
||||
}
|
||||
|
||||
extension Defaults.SetAlgebraSerializable where Element: Defaults.Serializable & Hashable {
|
||||
public static var bridge: Defaults.SetAlgebraBridge<Self> { Defaults.SetAlgebraBridge() }
|
||||
}
|
||||
|
||||
extension Set: Defaults.Serializable where Element: Defaults.Serializable {
|
||||
public static var bridge: Defaults.SetBridge<Element> { Defaults.SetBridge() }
|
||||
}
|
||||
|
||||
|
||||
extension Array: Defaults.Serializable where Element: Defaults.Serializable {
|
||||
public static var isNativelySupportedType: Bool { Element.isNativelySupportedType }
|
||||
public static var bridge: Defaults.ArrayBridge<Element> { Defaults.ArrayBridge() }
|
||||
}
|
||||
|
||||
extension Dictionary: Defaults.Serializable where Key: LosslessStringConvertible & Hashable, Value: Defaults.Serializable {
|
||||
public static var isNativelySupportedType: Bool { Value.isNativelySupportedType }
|
||||
public static var bridge: Defaults.DictionaryBridge<Key, Value> { Defaults.DictionaryBridge() }
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
/// `NSColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`
|
||||
extension NSColor: Defaults.Serializable {}
|
||||
#else
|
||||
/// `UIColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`
|
||||
extension UIColor: Defaults.Serializable {}
|
||||
#endif
|
|
@ -0,0 +1,93 @@
|
|||
import Foundation
|
||||
|
||||
/**
|
||||
All type that able to work with `Defaults` should conform this protocol.
|
||||
|
||||
It should have a static variable bridge which protocol should conform to `Defaults.Bridge`.
|
||||
|
||||
```
|
||||
struct User {
|
||||
username: String
|
||||
password: String
|
||||
}
|
||||
|
||||
extension User: Defaults.Serializable {
|
||||
static let bridge = UserBridge()
|
||||
}
|
||||
```
|
||||
*/
|
||||
public protocol DefaultsSerializable {
|
||||
typealias Value = Bridge.Value
|
||||
typealias Serializable = Bridge.Serializable
|
||||
associatedtype Bridge: DefaultsBridge
|
||||
|
||||
/// Static bridge for the `Value` which cannot store natively
|
||||
static var bridge: Bridge { get }
|
||||
|
||||
/// A flag to determine whether `Value` can be store natively or not
|
||||
static var isNativelySupportedType: Bool { get }
|
||||
}
|
||||
|
||||
/**
|
||||
A Bridge can do the serialization and de-serialization.
|
||||
|
||||
Have two associate types `Value` and `Serializable`.
|
||||
|
||||
- `Value`: the type user want to use it.
|
||||
- `Serializable`: the type stored in `UserDefaults`.
|
||||
- `serialize`: will be executed before storing to the `UserDefaults` .
|
||||
- `deserialize`: will be executed after retrieving its value from the `UserDefaults`.
|
||||
|
||||
```
|
||||
struct User {
|
||||
username: String
|
||||
password: String
|
||||
}
|
||||
|
||||
struct UserBridge: Defaults.Bridge {
|
||||
typealias Value = User
|
||||
typealias Serializable = [String: String]
|
||||
|
||||
func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ["username": value.username, "password": value.password]
|
||||
}
|
||||
|
||||
func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard
|
||||
let object = object,
|
||||
let username = object["username"],
|
||||
let password = object["password"]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return User(username: username, password: password)
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public protocol DefaultsBridge {
|
||||
associatedtype Value
|
||||
associatedtype Serializable
|
||||
|
||||
func serialize(_ value: Value?) -> Serializable?
|
||||
|
||||
func deserialize(_ object: Serializable?) -> Value?
|
||||
}
|
||||
|
||||
public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable {
|
||||
/// `Collection` does not have initializer, but we need initializer to convert an array into the `Value`
|
||||
init(_ elements: [Element])
|
||||
}
|
||||
|
||||
public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializable {
|
||||
/// Since `SetAlgebra` protocol does not conform to `Sequence`, we cannot convert a `SetAlgebra` to an `Array` directly.
|
||||
func toArray() -> [Element]
|
||||
}
|
||||
|
||||
/// Convenience protocol for `Codable`
|
||||
public protocol DefaultsCodableBridge: DefaultsBridge where Serializable == String, Value: Codable {}
|
|
@ -16,16 +16,15 @@ extension DefaultsBaseKey {
|
|||
public enum Defaults {
|
||||
public typealias BaseKey = DefaultsBaseKey
|
||||
public typealias AnyKey = Keys
|
||||
public typealias Serializable = DefaultsSerializable
|
||||
public typealias CollectionSerializable = DefaultsCollectionSerializable
|
||||
public typealias SetAlgebraSerializable = DefaultsSetAlgebraSerializable
|
||||
public typealias Bridge = DefaultsBridge
|
||||
typealias CodableBridge = DefaultsCodableBridge
|
||||
|
||||
public class Keys: BaseKey {
|
||||
public typealias Key = Defaults.Key
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public typealias NSSecureCodingKey = Defaults.NSSecureCodingKey
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public typealias NSSecureCodingOptionalKey = Defaults.NSSecureCodingOptionalKey
|
||||
|
||||
public let name: String
|
||||
public let suite: UserDefaults
|
||||
|
||||
|
@ -35,7 +34,7 @@ public enum Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
public final class Key<Value: Codable>: AnyKey {
|
||||
public final class Key<Value: Serializable>: AnyKey {
|
||||
public let defaultValue: Value
|
||||
|
||||
/// Create a defaults key.
|
||||
|
@ -49,67 +48,16 @@ public enum Defaults {
|
|||
return
|
||||
}
|
||||
|
||||
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
|
||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
||||
suite.register(defaults: [key: defaultValue])
|
||||
} else if let value = suite._encode(defaultValue) {
|
||||
suite.register(defaults: [key: value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public final class NSSecureCodingKey<Value: NSSecureCoding>: AnyKey {
|
||||
public let defaultValue: Value
|
||||
|
||||
/// Create a defaults key.
|
||||
/// The `default` parameter can be left out if the `Value` type is an optional.
|
||||
public init(_ key: String, default defaultValue: Value, suite: UserDefaults = .standard) {
|
||||
self.defaultValue = defaultValue
|
||||
|
||||
super.init(name: key, suite: suite)
|
||||
|
||||
if (defaultValue as? _DefaultsOptionalType)?.isNil == true {
|
||||
guard let serialized = Value.toSerializable(defaultValue) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
|
||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
||||
suite.register(defaults: [key: defaultValue])
|
||||
} else if let value = try? NSKeyedArchiver.archivedData(withRootObject: defaultValue, requiringSecureCoding: true) {
|
||||
suite.register(defaults: [key: value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public final class NSSecureCodingOptionalKey<Value: NSSecureCoding>: AnyKey {
|
||||
/// Create an optional defaults key.
|
||||
public init(_ key: String, suite: UserDefaults = .standard) {
|
||||
super.init(name: key, suite: suite)
|
||||
suite.register(defaults: [self.name: serialized])
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a defaults value using a `Defaults.Key`.
|
||||
public static subscript<Value>(key: Key<Value>) -> Value {
|
||||
get { key.suite[key] }
|
||||
set {
|
||||
key.suite[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a defaults value using a `Defaults.NSSecureCodingKey`.
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static subscript<Value>(key: NSSecureCodingKey<Value>) -> Value {
|
||||
get { key.suite[key] }
|
||||
set {
|
||||
key.suite[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a defaults value using a `Defaults.NSSecureCodingOptionalKey`.
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static subscript<Value>(key: NSSecureCodingOptionalKey<Value>) -> Value? {
|
||||
public static subscript<Value: Serializable>(key: Key<Value>) -> Value {
|
||||
get { key.suite[key] }
|
||||
set {
|
||||
key.suite[key] = newValue
|
||||
|
@ -129,14 +77,7 @@ extension Defaults {
|
|||
}
|
||||
|
||||
extension Defaults.Key {
|
||||
public convenience init<T>(_ key: String, suite: UserDefaults = .standard) where Value == T? {
|
||||
self.init(key, default: nil, suite: suite)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
extension Defaults.NSSecureCodingKey where Value: _DefaultsOptionalType {
|
||||
public convenience init(_ key: String, suite: UserDefaults = .standard) {
|
||||
public convenience init<T: Defaults.Serializable>(_ key: String, suite: UserDefaults = .standard) where Value == T? {
|
||||
self.init(key, default: nil, suite: suite)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import Foundation
|
||||
|
||||
extension Defaults {
|
||||
public enum Version: Int {
|
||||
case v5 = 5
|
||||
}
|
||||
|
||||
/**
|
||||
Migration the given key's value from json string to `Value`.
|
||||
|
||||
```
|
||||
extension Defaults.Keys {
|
||||
static let array = Key<Set<String>?>("array")
|
||||
}
|
||||
|
||||
Defaults.migrate(.array, to: .v5)
|
||||
```
|
||||
*/
|
||||
public static func migrate<Value: Defaults.Serializable & Codable>(_ keys: Key<Value>..., to version: Version) {
|
||||
migrate(keys, to: version)
|
||||
}
|
||||
|
||||
public static func migrate<Value: Defaults.NativeType>(_ keys: Key<Value>..., to version: Version) {
|
||||
migrate(keys, to: version)
|
||||
}
|
||||
|
||||
public static func migrate<Value: Defaults.Serializable & Codable>(_ keys: [Key<Value>], to version: Version) {
|
||||
switch version {
|
||||
case .v5:
|
||||
for key in keys {
|
||||
let suite = key.suite
|
||||
suite.migrateCodableToNative(forKey: key.name, of: Value.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func migrate<Value: Defaults.NativeType>(_ keys: [Key<Value>], to version: Version) {
|
||||
switch version {
|
||||
case .v5:
|
||||
for key in keys {
|
||||
let suite = key.suite
|
||||
suite.migrateCodableToNative(forKey: key.name, of: Value.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
extension Defaults {
|
||||
public typealias NativeType = DefaultsNativeType
|
||||
public typealias CodableType = DefaultsCodableType
|
||||
}
|
||||
|
||||
extension Data: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Data: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Date: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Date: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Bool: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Bool: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Int: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension UInt: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Double: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Double: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Float: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Float: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension String: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension String: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension CGFloat: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension CGFloat: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Int8: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Int8: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt8: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension UInt8: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Int16: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Int16: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt16: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension UInt16: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Int32: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Int32: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt32: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension UInt32: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Int64: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension Int64: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt64: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension UInt64: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension URL: Defaults.NativeType {
|
||||
public typealias CodableForm = Self
|
||||
}
|
||||
extension URL: Defaults.CodableType {
|
||||
public typealias NativeForm = Self
|
||||
|
||||
public func toNative() -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: Defaults.NativeType where Wrapped: Defaults.NativeType {
|
||||
public typealias CodableForm = Wrapped.CodableForm
|
||||
}
|
||||
|
||||
extension Defaults.CollectionSerializable where Self: Defaults.NativeType, Element: Defaults.NativeType {
|
||||
public typealias CodableForm = [Element.CodableForm]
|
||||
}
|
||||
|
||||
extension Defaults.SetAlgebraSerializable where Self: Defaults.NativeType, Element: Defaults.NativeType {
|
||||
public typealias CodableForm = [Element.CodableForm]
|
||||
}
|
||||
|
||||
extension Defaults.CodableType where Self: RawRepresentable, NativeForm: RawRepresentable, Self.RawValue == NativeForm.RawValue {
|
||||
public func toNative() -> NativeForm {
|
||||
NativeForm(rawValue: self.rawValue)!
|
||||
}
|
||||
}
|
||||
|
||||
extension Set: Defaults.NativeType where Element: Defaults.NativeType {
|
||||
public typealias CodableForm = [Element.CodableForm]
|
||||
}
|
||||
|
||||
extension Array: Defaults.NativeType where Element: Defaults.NativeType {
|
||||
public typealias CodableForm = [Element.CodableForm]
|
||||
}
|
||||
extension Array: Defaults.CodableType where Element: Defaults.CodableType {
|
||||
public typealias NativeForm = [Element.NativeForm]
|
||||
|
||||
public func toNative() -> NativeForm {
|
||||
map { $0.toNative() }
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary: Defaults.NativeType where Key: LosslessStringConvertible & Hashable, Value: Defaults.NativeType {
|
||||
public typealias CodableForm = [String: Value.CodableForm]
|
||||
}
|
||||
extension Dictionary: Defaults.CodableType where Key == String, Value: Defaults.CodableType {
|
||||
public typealias NativeForm = [String: Value.NativeForm]
|
||||
|
||||
public func toNative() -> NativeForm {
|
||||
reduce(into: NativeForm()) { memo, tuple in
|
||||
memo[tuple.key] = tuple.value.toNative()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import Foundation
|
||||
|
||||
/**
|
||||
Only for migration.
|
||||
|
||||
Represents the type after migration and its protocol should conform to `Defaults.Serializable`.
|
||||
|
||||
It should have an associated type name `CodableForm` which protocol conform to `Codable`.
|
||||
So we can convert the json string into `NativeType` like this.
|
||||
```
|
||||
guard
|
||||
let jsonString = string,
|
||||
let jsonData = jsonString.data(using: .utf8),
|
||||
let codable = try? JSONDecoder().decode(NativeType.CodableForm.self, from: jsonData)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return codable.toNative()
|
||||
```
|
||||
*/
|
||||
public protocol DefaultsNativeType: Defaults.Serializable {
|
||||
associatedtype CodableForm: Defaults.CodableType
|
||||
}
|
||||
|
||||
/**
|
||||
Only for migration.
|
||||
|
||||
Represents the type before migration an its protocol should conform to `Codable`.
|
||||
|
||||
The main purposed of `CodableType` is trying to infer the `Codable` type to do `JSONDecoder().decode`
|
||||
It should have an associated type name `NativeForm` which is the type we want it to store in `UserDefaults`.
|
||||
And it also have a `toNative()` function to convert itself into `NativeForm`.
|
||||
|
||||
```
|
||||
struct User {
|
||||
username: String
|
||||
password: String
|
||||
}
|
||||
|
||||
struct CodableUser: Codable {
|
||||
username: String
|
||||
password: String
|
||||
}
|
||||
|
||||
extension User: Defaults.NativeType {
|
||||
typealias CodableForm = CodableUser
|
||||
}
|
||||
|
||||
extension CodableUser: Defaults.CodableType {
|
||||
typealias NativeForm = User
|
||||
|
||||
func toNative() -> NativeForm {
|
||||
User(username: self.username, password: self.password)
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public protocol DefaultsCodableType: Codable {
|
||||
associatedtype NativeForm: Defaults.NativeType
|
||||
func toNative() -> NativeForm
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import Foundation
|
||||
|
||||
extension UserDefaults {
|
||||
func migrateCodableToNative<Value: Defaults.Serializable & Codable>(forKey key: String, of type: Value.Type) {
|
||||
guard
|
||||
let jsonString = string(forKey: key),
|
||||
let jsonData = jsonString.data(using: .utf8),
|
||||
let codable = try? JSONDecoder().decode(Value.self, from: jsonData)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
_set(key, to: codable)
|
||||
}
|
||||
|
||||
/**
|
||||
Get json string in `UserDefaults` and decode it into the `NativeForm`.
|
||||
|
||||
How it works?
|
||||
For example:
|
||||
Step1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`.
|
||||
`JSONDecoder().decode([String].CodableForm.self, from: jsonData)`
|
||||
|
||||
Step2. `Array`conform to `NativeType`, its `CodableForm` is `[Element.CodableForm]` and `Element` is `String`.
|
||||
`JSONDecoder().decode([String.CodableForm].self, from: jsonData)`
|
||||
|
||||
Step3. `String`'s `CodableForm` is `self`, because `String` is `Codable`.
|
||||
`JSONDecoder().decode([String].self, from: jsonData)`
|
||||
*/
|
||||
func migrateCodableToNative<Value: Defaults.NativeType>(forKey key: String, of type: Value.Type) {
|
||||
guard
|
||||
let jsonString = string(forKey: key),
|
||||
let jsonData = jsonString.data(using: .utf8),
|
||||
let codable = try? JSONDecoder().decode(Value.CodableForm.self, from: jsonData)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
_set(key, to: codable.toNative())
|
||||
}
|
||||
}
|
|
@ -88,7 +88,7 @@ extension Defaults {
|
|||
```
|
||||
*/
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
public static func publisher<Value>(
|
||||
public static func publisher<Value: Serializable>(
|
||||
_ key: Key<Value>,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<KeyChange<Value>, Never> {
|
||||
|
@ -98,34 +98,6 @@ extension Defaults {
|
|||
return AnyPublisher(publisher)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a type-erased `Publisher` that publishes changes related to the given key.
|
||||
*/
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
public static func publisher<Value>(
|
||||
_ key: NSSecureCodingKey<Value>,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<NSSecureCodingKeyChange<Value>, Never> {
|
||||
let publisher = DefaultsPublisher(suite: key.suite, key: key.name, options: options)
|
||||
.map { NSSecureCodingKeyChange<Value>(change: $0, defaultValue: key.defaultValue) }
|
||||
|
||||
return AnyPublisher(publisher)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a type-erased `Publisher` that publishes changes related to the given optional key.
|
||||
*/
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
public static func publisher<Value>(
|
||||
_ key: NSSecureCodingOptionalKey<Value>,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<NSSecureCodingOptionalKeyChange<Value>, Never> {
|
||||
let publisher = DefaultsPublisher(suite: key.suite, key: key.name, options: options)
|
||||
.map { NSSecureCodingOptionalKeyChange<Value>(change: $0) }
|
||||
|
||||
return AnyPublisher(publisher)
|
||||
}
|
||||
|
||||
/**
|
||||
Publisher for multiple `Key<T>` observation, but without specific information about changes.
|
||||
*/
|
||||
|
|
|
@ -37,7 +37,7 @@ extension Defaults {
|
|||
|
||||
public typealias ObservationOptions = Set<ObservationOption>
|
||||
|
||||
private static func deserialize<Value: Decodable>(_ value: Any?, to type: Value.Type) -> Value? {
|
||||
private static func deserialize<Value: Serializable>(_ value: Any?, to type: Value.Type) -> Value? {
|
||||
guard
|
||||
let value = value,
|
||||
!(value is NSNull)
|
||||
|
@ -45,34 +45,7 @@ extension Defaults {
|
|||
return nil
|
||||
}
|
||||
|
||||
// This handles the case where the value was a plist value using `isNativelySupportedType`
|
||||
if let value = value as? Value {
|
||||
return value
|
||||
}
|
||||
|
||||
// Using the array trick as done below in `UserDefaults#_set()`
|
||||
return [Value].init(jsonString: "\([value])")?.first
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private static func deserialize<Value: NSSecureCoding>(_ value: Any?, to type: Value.Type) -> Value? {
|
||||
guard
|
||||
let value = value,
|
||||
!(value is NSNull)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This handles the case where the value was a plist value using `isNativelySupportedType`
|
||||
if let value = value as? Value {
|
||||
return value
|
||||
}
|
||||
|
||||
guard let dataValue = value as? Data else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(dataValue) as? Value
|
||||
return Value.toValue(value)
|
||||
}
|
||||
|
||||
struct BaseChange {
|
||||
|
@ -91,7 +64,7 @@ extension Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
public struct KeyChange<Value: Codable> {
|
||||
public struct KeyChange<Value: Serializable> {
|
||||
public let kind: NSKeyValueChange
|
||||
public let indexes: IndexSet?
|
||||
public let isPrior: Bool
|
||||
|
@ -107,40 +80,6 @@ extension Defaults {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public struct NSSecureCodingKeyChange<Value: NSSecureCoding> {
|
||||
public let kind: NSKeyValueChange
|
||||
public let indexes: IndexSet?
|
||||
public let isPrior: Bool
|
||||
public let newValue: Value
|
||||
public let oldValue: Value
|
||||
|
||||
init(change: BaseChange, defaultValue: Value) {
|
||||
self.kind = change.kind
|
||||
self.indexes = change.indexes
|
||||
self.isPrior = change.isPrior
|
||||
self.oldValue = deserialize(change.oldValue, to: Value.self) ?? defaultValue
|
||||
self.newValue = deserialize(change.newValue, to: Value.self) ?? defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public struct NSSecureCodingOptionalKeyChange<Value: NSSecureCoding> {
|
||||
public let kind: NSKeyValueChange
|
||||
public let indexes: IndexSet?
|
||||
public let isPrior: Bool
|
||||
public let newValue: Value?
|
||||
public let oldValue: Value?
|
||||
|
||||
init(change: BaseChange) {
|
||||
self.kind = change.kind
|
||||
self.indexes = change.indexes
|
||||
self.isPrior = change.isPrior
|
||||
self.oldValue = deserialize(change.oldValue, to: Value.self)
|
||||
self.newValue = deserialize(change.newValue, to: Value.self)
|
||||
}
|
||||
}
|
||||
|
||||
private static var preventPropagationThreadDictionaryKey: String {
|
||||
"\(type(of: Observation.self))_threadUpdatingValuesFlag"
|
||||
}
|
||||
|
@ -242,7 +181,6 @@ extension Defaults {
|
|||
guard !updatingValuesFlag else {
|
||||
return
|
||||
}
|
||||
|
||||
callback(BaseChange(change: change))
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +290,7 @@ extension Defaults {
|
|||
}
|
||||
```
|
||||
*/
|
||||
public static func observe<Value>(
|
||||
public static func observe<Value: Serializable>(
|
||||
_ key: Key<Value>,
|
||||
options: ObservationOptions = [.initial],
|
||||
handler: @escaping (KeyChange<Value>) -> Void
|
||||
|
@ -366,41 +304,6 @@ extension Defaults {
|
|||
return observation
|
||||
}
|
||||
|
||||
/**
|
||||
Observe a defaults key.
|
||||
*/
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func observe<Value>(
|
||||
_ key: NSSecureCodingKey<Value>,
|
||||
options: ObservationOptions = [.initial],
|
||||
handler: @escaping (NSSecureCodingKeyChange<Value>) -> Void
|
||||
) -> Observation {
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
handler(
|
||||
NSSecureCodingKeyChange(change: change, defaultValue: key.defaultValue)
|
||||
)
|
||||
}
|
||||
observation.start(options: options)
|
||||
return observation
|
||||
}
|
||||
|
||||
/**
|
||||
Observe an optional defaults key.
|
||||
*/
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public static func observe<Value>(
|
||||
_ key: NSSecureCodingOptionalKey<Value>,
|
||||
options: ObservationOptions = [.initial],
|
||||
handler: @escaping (NSSecureCodingOptionalKeyChange<Value>) -> Void
|
||||
) -> Observation {
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
handler(
|
||||
NSSecureCodingOptionalKeyChange(change: change)
|
||||
)
|
||||
}
|
||||
observation.start(options: options)
|
||||
return observation
|
||||
}
|
||||
|
||||
/**
|
||||
Observe multiple keys of any type, but without any information about the changes.
|
||||
|
@ -447,10 +350,4 @@ extension Defaults.ObservationOptions {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
extension Defaults.NSSecureCodingKeyChange: Equatable where Value: Equatable { }
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
extension Defaults.NSSecureCodingOptionalKeyChange: Equatable where Value: Equatable { }
|
||||
|
||||
extension Defaults.KeyChange: Equatable where Value: Equatable { }
|
||||
|
|
|
@ -4,7 +4,7 @@ import Combine
|
|||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
extension Defaults {
|
||||
final class Observable<Value: Codable>: ObservableObject {
|
||||
final class Observable<Value: Serializable>: ObservableObject {
|
||||
let objectWillChange = ObservableObjectPublisher()
|
||||
private var observation: DefaultsObservation?
|
||||
private let key: Defaults.Key<Value>
|
||||
|
@ -40,7 +40,7 @@ extension Defaults {
|
|||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
@propertyWrapper
|
||||
public struct Default<Value: Codable>: DynamicProperty {
|
||||
public struct Default<Value: Defaults.Serializable>: DynamicProperty {
|
||||
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
|
||||
|
||||
private let key: Defaults.Key<Value>
|
||||
|
|
|
@ -1,136 +1,29 @@
|
|||
import Foundation
|
||||
|
||||
extension UserDefaults {
|
||||
private func _get<Value: Codable>(_ key: String) -> Value? {
|
||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
||||
return object(forKey: key) as? Value
|
||||
}
|
||||
|
||||
guard
|
||||
let text = string(forKey: key),
|
||||
let data = "[\(text)]".data(using: .utf8)
|
||||
else {
|
||||
func _get<Value: Defaults.Serializable>(_ key: String) -> Value? {
|
||||
guard let anyObject = object(forKey: key) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
return (try JSONDecoder().decode([Value].self, from: data)).first
|
||||
} catch {
|
||||
print(error)
|
||||
return Value.toValue(anyObject)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private func _get<Value: NSSecureCoding>(_ key: String) -> Value? {
|
||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
||||
return object(forKey: key) as? Value
|
||||
}
|
||||
|
||||
guard
|
||||
let data = data(forKey: key)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Value
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func _encode<Value: Codable>(_ value: Value) -> String? {
|
||||
do {
|
||||
// Some codable values like URL and enum are encoded as a top-level
|
||||
// string which JSON can't handle, so we need to wrap it in an array
|
||||
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750
|
||||
let data = try JSONEncoder().encode([value])
|
||||
return String(String(data: data, encoding: .utf8)!.dropFirst().dropLast())
|
||||
} catch {
|
||||
print(error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func _set<Value: Codable>(_ key: String, to value: Value) {
|
||||
func _set<Value: Defaults.Serializable>(_ key: String, to value: Value) {
|
||||
if (value as? _DefaultsOptionalType)?.isNil == true {
|
||||
removeObject(forKey: key)
|
||||
return
|
||||
}
|
||||
|
||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
||||
set(value, forKey: key)
|
||||
return
|
||||
set(Value.toSerializable(value), forKey: key)
|
||||
}
|
||||
|
||||
set(_encode(value), forKey: key)
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private func _set<Value: NSSecureCoding>(_ key: String, to value: Value) {
|
||||
// TODO: Handle nil here too.
|
||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
||||
set(value, forKey: key)
|
||||
return
|
||||
}
|
||||
|
||||
set(try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true), forKey: key)
|
||||
}
|
||||
|
||||
public subscript<Value>(key: Defaults.Key<Value>) -> Value {
|
||||
public subscript<Value: Defaults.Serializable>(key: Defaults.Key<Value>) -> Value {
|
||||
get { _get(key.name) ?? key.defaultValue }
|
||||
set {
|
||||
_set(key.name, to: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public subscript<Value>(key: Defaults.NSSecureCodingKey<Value>) -> Value {
|
||||
get { _get(key.name) ?? key.defaultValue }
|
||||
set {
|
||||
_set(key.name, to: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
public subscript<Value>(key: Defaults.NSSecureCodingOptionalKey<Value>) -> Value? {
|
||||
get { _get(key.name) }
|
||||
set {
|
||||
guard let value = newValue else {
|
||||
set(nil, forKey: key.name)
|
||||
return
|
||||
}
|
||||
|
||||
_set(key.name, to: value)
|
||||
}
|
||||
}
|
||||
|
||||
static func isNativelySupportedType<T>(_ type: T.Type) -> Bool {
|
||||
switch type {
|
||||
case
|
||||
is Bool.Type,
|
||||
is Bool?.Type, // swiftlint:disable:this discouraged_optional_boolean
|
||||
is String.Type,
|
||||
is String?.Type,
|
||||
is Int.Type,
|
||||
is Int?.Type,
|
||||
is Double.Type,
|
||||
is Double?.Type,
|
||||
is Float.Type,
|
||||
is Float?.Type,
|
||||
is Date.Type,
|
||||
is Date?.Type,
|
||||
is Data.Type,
|
||||
is Data?.Type:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UserDefaults {
|
||||
|
|
|
@ -146,3 +146,55 @@ extension DispatchQueue {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence {
|
||||
/// Returns an array containing the non-nil elements.
|
||||
func compact<T>() -> [T] where Element == T? {
|
||||
// TODO: Make this `compactMap(\.self)` when https://bugs.swift.org/browse/SR-12897 is fixed.
|
||||
compactMap { $0 }
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults.Serializable {
|
||||
/**
|
||||
Cast `Serializable` value to `Self`.
|
||||
Convert the native support type from `UserDefaults` into `Self`.
|
||||
|
||||
```
|
||||
guard let anyObject = object(forKey: key) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value.toValue(anyObject)
|
||||
```
|
||||
*/
|
||||
static func toValue(_ anyObject: Any) -> Self? {
|
||||
// Return directly if `anyObject` can cast to Value, means `Value` is Native supported type.
|
||||
if Self.isNativelySupportedType, let anyObject = anyObject as? Self {
|
||||
return anyObject
|
||||
} else if let value = Self.bridge.deserialize(anyObject as? Serializable) {
|
||||
return value as? Self
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
Cast `Self` to `Serializable`.
|
||||
Convert `Self` into `UserDefaults` native support type.
|
||||
|
||||
```
|
||||
set(Value.toSerialize(value), forKey: key)
|
||||
```
|
||||
*/
|
||||
static func toSerializable(_ value: Self) -> Any? {
|
||||
// Return directly if `Self` is native supported type, since it does not need serialization.
|
||||
if Self.isNativelySupportedType {
|
||||
return value
|
||||
} else if let serialized = Self.bridge.serialize(value as? Self.Value) {
|
||||
return serialized
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
|
||||
private let fixtureArray = ["Hank", "Chen"]
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let array = Key<[String]>("array", default: fixtureArray)
|
||||
}
|
||||
|
||||
final class DefaultsArrayTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<[String]>("independentArrayStringKey", default: fixtureArray)
|
||||
XCTAssertEqual(Defaults[key][0], fixtureArray[0])
|
||||
let newValue = "John"
|
||||
Defaults[key][0] = newValue
|
||||
XCTAssertEqual(Defaults[key][0], newValue)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<[String]?>("independentArrayOptionalStringKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = fixtureArray
|
||||
XCTAssertEqual(Defaults[key]?[0], fixtureArray[0])
|
||||
Defaults[key] = nil
|
||||
XCTAssertNil(Defaults[key])
|
||||
let newValue = ["John", "Chen"]
|
||||
Defaults[key] = newValue
|
||||
XCTAssertEqual(Defaults[key]?[0], newValue[0])
|
||||
}
|
||||
|
||||
func testNestedKey() {
|
||||
let defaultValue = ["Hank", "Chen"]
|
||||
let key = Defaults.Key<[[String]]>("independentArrayNestedKey", default: [defaultValue])
|
||||
XCTAssertEqual(Defaults[key][0][0], "Hank")
|
||||
let newValue = ["Sindre", "Sorhus"]
|
||||
Defaults[key][0] = newValue
|
||||
Defaults[key].append(defaultValue)
|
||||
XCTAssertEqual(Defaults[key][0][0], newValue[0])
|
||||
XCTAssertEqual(Defaults[key][0][1], newValue[1])
|
||||
XCTAssertEqual(Defaults[key][1][0], defaultValue[0])
|
||||
XCTAssertEqual(Defaults[key][1][1], defaultValue[1])
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let defaultValue = ["0": "HankChen"]
|
||||
let key = Defaults.Key<[[String: String]]>("independentArrayDictionaryKey", default: [defaultValue])
|
||||
XCTAssertEqual(Defaults[key][0]["0"], defaultValue["0"])
|
||||
let newValue = ["0": "SindreSorhus"]
|
||||
Defaults[key][0] = newValue
|
||||
Defaults[key].append(defaultValue)
|
||||
XCTAssertEqual(Defaults[key][0]["0"], newValue["0"])
|
||||
XCTAssertEqual(Defaults[key][1]["0"], defaultValue["0"])
|
||||
}
|
||||
|
||||
func testNestedDictionaryKey() {
|
||||
let defaultValue = ["0": [["0": 0]]]
|
||||
let key = Defaults.Key<[[String: [[String: Int]]]]>("independentArrayNestedDictionaryKey", default: [defaultValue])
|
||||
XCTAssertEqual(Defaults[key][0]["0"]![0]["0"], 0)
|
||||
let newValue = 1
|
||||
Defaults[key][0]["0"]![0]["0"] = newValue
|
||||
Defaults[key].append(defaultValue)
|
||||
XCTAssertEqual(Defaults[key][1]["0"]![0]["0"], 0)
|
||||
XCTAssertEqual(Defaults[key][0]["0"]![0]["0"], newValue)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssertEqual(Defaults[.array][0], fixtureArray[0])
|
||||
let newName = "Hank121314"
|
||||
Defaults[.array][0] = newName
|
||||
XCTAssertEqual(Defaults[.array][0], newName)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<[String]>("observeArrayKeyCombine", default: fixtureArray)
|
||||
let newName = "Chen"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureArray[0], newName), (newName, fixtureArray[0])].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = newName
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<[String]?>("observeArrayOptionalKeyCombine")
|
||||
let newName = ["Chen"]
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
// swiftlint:disable discouraged_optional_collection
|
||||
let expectedValues: [([String]?, [String]?)] = [(nil, fixtureArray), (fixtureArray, newName), (newName, nil)]
|
||||
|
||||
let cancellable = publisher.sink { actualValues in
|
||||
for (expected, actual) in zip(expectedValues, actualValues) {
|
||||
XCTAssertEqual(expected.0, actual.0)
|
||||
XCTAssertEqual(expected.1, actual.1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureArray
|
||||
Defaults[key] = newName
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<[String]>("observeArrayKey", default: fixtureArray)
|
||||
let newName = "John"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, fixtureArray)
|
||||
XCTAssertEqual(change.newValue, [fixtureArray[0], newName])
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][1] = newName
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<[String]?>("observeArrayOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue!, fixtureArray)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureArray
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
|
||||
private enum FixtureCodableEnum: String, Defaults.Serializable & Codable & Hashable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let codableEnum = Key<FixtureCodableEnum>("codable_enum", default: .oneHour)
|
||||
fileprivate static let codableEnumArray = Key<[FixtureCodableEnum]>("codable_enum", default: [.oneHour])
|
||||
fileprivate static let codableEnumDictionary = Key<[String: FixtureCodableEnum]>("codable_enum", default: ["0": .oneHour])
|
||||
}
|
||||
|
||||
final class DefaultsCodableEnumTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<FixtureCodableEnum>("independentCodableEnumKey", default: .tenMinutes)
|
||||
XCTAssertEqual(Defaults[key], .tenMinutes)
|
||||
Defaults[key] = .halfHour
|
||||
XCTAssertEqual(Defaults[key], .halfHour)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<FixtureCodableEnum?>("independentCodableEnumOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = .tenMinutes
|
||||
XCTAssertEqual(Defaults[key], .tenMinutes)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[FixtureCodableEnum]>("independentCodableEnumArrayKey", default: [.tenMinutes])
|
||||
XCTAssertEqual(Defaults[key][0], .tenMinutes)
|
||||
Defaults[key][0] = .halfHour
|
||||
XCTAssertEqual(Defaults[key][0], .halfHour)
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[FixtureCodableEnum]?>("independentCodableEnumArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [.halfHour]
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[FixtureCodableEnum]]>("independentCodableEnumNestedArrayKey", default: [[.tenMinutes]])
|
||||
XCTAssertEqual(Defaults[key][0][0], .tenMinutes)
|
||||
Defaults[key].append([.halfHour])
|
||||
Defaults[key][0].append(.oneHour)
|
||||
XCTAssertEqual(Defaults[key][1][0], .halfHour)
|
||||
XCTAssertEqual(Defaults[key][0][1], .oneHour)
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: FixtureCodableEnum]]>("independentCodableEnumArrayDictionaryKey", default: [["0": .tenMinutes]])
|
||||
XCTAssertEqual(Defaults[key][0]["0"], .tenMinutes)
|
||||
Defaults[key][0]["1"] = .halfHour
|
||||
Defaults[key].append(["0": .oneHour])
|
||||
XCTAssertEqual(Defaults[key][0]["1"], .halfHour)
|
||||
XCTAssertEqual(Defaults[key][1]["0"], .oneHour)
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: FixtureCodableEnum]>("independentCodableEnumDictionaryKey", default: ["0": .tenMinutes])
|
||||
XCTAssertEqual(Defaults[key]["0"], .tenMinutes)
|
||||
Defaults[key]["1"] = .halfHour
|
||||
Defaults[key]["0"] = .oneHour
|
||||
XCTAssertEqual(Defaults[key]["0"], .oneHour)
|
||||
XCTAssertEqual(Defaults[key]["1"], .halfHour)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: FixtureCodableEnum]?>("independentCodableEnumDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": .tenMinutes]
|
||||
Defaults[key]?["1"] = .halfHour
|
||||
XCTAssertEqual(Defaults[key]?["0"], .tenMinutes)
|
||||
XCTAssertEqual(Defaults[key]?["1"], .halfHour)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [FixtureCodableEnum]]>("independentCodableEnumDictionaryArrayKey", default: ["0": [.tenMinutes]])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], .tenMinutes)
|
||||
Defaults[key]["0"]?.append(.halfHour)
|
||||
Defaults[key]["1"] = [.oneHour]
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], .tenMinutes)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], .halfHour)
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0], .oneHour)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssertEqual(Defaults[.codableEnum], .oneHour)
|
||||
Defaults[.codableEnum] = .tenMinutes
|
||||
XCTAssertEqual(Defaults[.codableEnum], .tenMinutes)
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
XCTAssertEqual(Defaults[.codableEnumArray][0], .oneHour)
|
||||
Defaults[.codableEnumArray].append(.halfHour)
|
||||
XCTAssertEqual(Defaults[.codableEnumArray][0], .oneHour)
|
||||
XCTAssertEqual(Defaults[.codableEnumArray][1], .halfHour)
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
XCTAssertEqual(Defaults[.codableEnumDictionary]["0"], .oneHour)
|
||||
Defaults[.codableEnumDictionary]["1"] = .halfHour
|
||||
XCTAssertEqual(Defaults[.codableEnumDictionary]["0"], .oneHour)
|
||||
XCTAssertEqual(Defaults[.codableEnumDictionary]["1"], .halfHour)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<FixtureCodableEnum>("observeCodableEnumKeyCombine", default: .tenMinutes)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(FixtureCodableEnum, FixtureCodableEnum)] = [(.tenMinutes, .oneHour), (.oneHour, .tenMinutes)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .oneHour
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<FixtureCodableEnum?>("observeCodableEnumOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(FixtureCodableEnum?, FixtureCodableEnum?)] = [(nil, .tenMinutes), (.tenMinutes, .halfHour), (.halfHour, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .tenMinutes
|
||||
Defaults[key] = .halfHour
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[FixtureCodableEnum]>("observeCodableEnumArrayKeyCombine", default: [.tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(FixtureCodableEnum?, FixtureCodableEnum?)] = [(.tenMinutes, .halfHour), (.halfHour, .tenMinutes)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = .halfHour
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: FixtureCodableEnum]>("observeCodableEnumDictionaryKeyCombine", default: ["0": .tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(FixtureCodableEnum?, FixtureCodableEnum?)] = [(.tenMinutes, .halfHour), (.halfHour, .tenMinutes)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = .halfHour
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<FixtureCodableEnum>("observeCodableEnumKey", default: .tenMinutes)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, .tenMinutes)
|
||||
XCTAssertEqual(change.newValue, .halfHour)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .halfHour
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<FixtureCodableEnum?>("observeCodableEnumOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue, .halfHour)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .halfHour
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[FixtureCodableEnum]>("observeCodableEnumArrayKey", default: [.tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0], .tenMinutes)
|
||||
XCTAssertEqual(change.newValue[1], .halfHour)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].append(.halfHour)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: FixtureCodableEnum]>("observeCodableEnumDictionaryKey", default: ["0": .tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"], .tenMinutes)
|
||||
XCTAssertEqual(change.newValue["1"], .halfHour)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = .halfHour
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
import Defaults
|
||||
|
||||
private struct Unicorn: Codable, Defaults.Serializable {
|
||||
var isUnicorn: Bool
|
||||
}
|
||||
|
||||
private let fixtureCodable = Unicorn(isUnicorn: true)
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let codable = Key<Unicorn>("codable", default: fixtureCodable)
|
||||
fileprivate static let codableArray = Key<[Unicorn]>("codable", default: [fixtureCodable])
|
||||
fileprivate static let codableDictionary = Key<[String: Unicorn]>("codable", default: ["0": fixtureCodable])
|
||||
}
|
||||
|
||||
final class DefaultsCodableTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<Unicorn>("independentCodableKey", default: fixtureCodable)
|
||||
XCTAssertTrue(Defaults[key].isUnicorn)
|
||||
Defaults[key].isUnicorn = false
|
||||
XCTAssertFalse(Defaults[key].isUnicorn)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<Unicorn?>("independentCodableOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = Unicorn(isUnicorn: true)
|
||||
XCTAssertTrue(Defaults[key]?.isUnicorn ?? false)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[Unicorn]>("independentCodableArrayKey", default: [fixtureCodable])
|
||||
XCTAssertTrue(Defaults[key][0].isUnicorn)
|
||||
Defaults[key].append(Unicorn(isUnicorn: false))
|
||||
XCTAssertTrue(Defaults[key][0].isUnicorn)
|
||||
XCTAssertFalse(Defaults[key][1].isUnicorn)
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[Unicorn]?>("independentCodableArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [fixtureCodable]
|
||||
Defaults[key]?.append(Unicorn(isUnicorn: false))
|
||||
XCTAssertTrue(Defaults[key]?[0].isUnicorn ?? false)
|
||||
XCTAssertFalse(Defaults[key]?[1].isUnicorn ?? false)
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[Unicorn]]>("independentCodableNestedArrayKey", default: [[fixtureCodable]])
|
||||
XCTAssertTrue(Defaults[key][0][0].isUnicorn)
|
||||
Defaults[key].append([fixtureCodable])
|
||||
Defaults[key][0].append(Unicorn(isUnicorn: false))
|
||||
XCTAssertTrue(Defaults[key][0][0].isUnicorn)
|
||||
XCTAssertTrue(Defaults[key][1][0].isUnicorn)
|
||||
XCTAssertFalse(Defaults[key][0][1].isUnicorn)
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: Unicorn]]>("independentCodableArrayDictionaryKey", default: [["0": fixtureCodable]])
|
||||
XCTAssertTrue(Defaults[key][0]["0"]?.isUnicorn ?? false)
|
||||
Defaults[key].append(["0": fixtureCodable])
|
||||
Defaults[key][0]["1"] = Unicorn(isUnicorn: false)
|
||||
XCTAssertTrue(Defaults[key][0]["0"]?.isUnicorn ?? false)
|
||||
XCTAssertTrue(Defaults[key][1]["0"]?.isUnicorn ?? false)
|
||||
XCTAssertFalse(Defaults[key][0]["1"]?.isUnicorn ?? true)
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: Unicorn]>("independentCodableDictionaryKey", default: ["0": fixtureCodable])
|
||||
XCTAssertTrue(Defaults[key]["0"]?.isUnicorn ?? false)
|
||||
Defaults[key]["1"] = Unicorn(isUnicorn: false)
|
||||
XCTAssertTrue(Defaults[key]["0"]?.isUnicorn ?? false)
|
||||
XCTAssertFalse(Defaults[key]["1"]?.isUnicorn ?? true)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: Unicorn]?>("independentCodableDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": fixtureCodable]
|
||||
Defaults[key]?["1"] = Unicorn(isUnicorn: false)
|
||||
XCTAssertTrue(Defaults[key]?["0"]?.isUnicorn ?? false)
|
||||
XCTAssertFalse(Defaults[key]?["1"]?.isUnicorn ?? true)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [Unicorn]]>("independentCodableDictionaryArrayKey", default: ["0": [fixtureCodable]])
|
||||
XCTAssertTrue(Defaults[key]["0"]?[0].isUnicorn ?? false)
|
||||
Defaults[key]["1"] = [fixtureCodable]
|
||||
Defaults[key]["0"]?.append(Unicorn(isUnicorn: false))
|
||||
XCTAssertTrue(Defaults[key]["1"]?[0].isUnicorn ?? false)
|
||||
XCTAssertFalse(Defaults[key]["0"]?[1].isUnicorn ?? true)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssertTrue(Defaults[.codable].isUnicorn)
|
||||
Defaults[.codable] = Unicorn(isUnicorn: false)
|
||||
XCTAssertFalse(Defaults[.codable].isUnicorn)
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
XCTAssertTrue(Defaults[.codableArray][0].isUnicorn)
|
||||
Defaults[.codableArray][0] = Unicorn(isUnicorn: false)
|
||||
XCTAssertFalse(Defaults[.codableArray][0].isUnicorn)
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
XCTAssertTrue(Defaults[.codableDictionary]["0"]?.isUnicorn ?? false)
|
||||
Defaults[.codableDictionary]["0"] = Unicorn(isUnicorn: false)
|
||||
XCTAssertFalse(Defaults[.codableDictionary]["0"]?.isUnicorn ?? true)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<Unicorn>("observeCodableKeyCombine", default: fixtureCodable)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue.isUnicorn, $0.newValue.isUnicorn) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(true, false), (false, true)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = Unicorn(isUnicorn: false)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<Unicorn?>("observeCodableOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue?.isUnicorn, $0.newValue?.isUnicorn) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(Bool?, Bool?)] = [(nil, true), (true, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureCodable
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[Unicorn]>("observeCodableArrayKeyCombine", default: [fixtureCodable])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(true, false), (false, true)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0].isUnicorn)
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0].isUnicorn)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = Unicorn(isUnicorn: false)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: Unicorn]>("observeCodableDictionaryKeyCombine", default: ["0": fixtureCodable])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(true, false), (false, true)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"]?.isUnicorn)
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"]?.isUnicorn)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = Unicorn(isUnicorn: false)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<Unicorn>("observeCodableKey", default: fixtureCodable)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue.isUnicorn)
|
||||
XCTAssertFalse(change.newValue.isUnicorn)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = Unicorn(isUnicorn: false)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<Unicorn?>("observeCodableOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertTrue(change.newValue?.isUnicorn ?? false)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureCodable
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[Unicorn]>("observeCodableArrayKey", default: [fixtureCodable])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue[0].isUnicorn)
|
||||
XCTAssertFalse(change.newValue[0].isUnicorn)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = Unicorn(isUnicorn: false)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: Unicorn]>("observeCodableDictionaryKey", default: ["0": fixtureCodable])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue["0"]?.isUnicorn ?? false)
|
||||
XCTAssertFalse(change.newValue["0"]?.isUnicorn ?? true)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = Unicorn(isUnicorn: false)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,351 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
import Defaults
|
||||
|
||||
private struct Item: Equatable {
|
||||
let name: String
|
||||
let count: UInt
|
||||
}
|
||||
|
||||
extension Item: Defaults.Serializable {
|
||||
static let bridge = ItemBridge()
|
||||
}
|
||||
|
||||
private struct ItemBridge: Defaults.Bridge {
|
||||
typealias Value = Item
|
||||
typealias Serializable = [String: String]
|
||||
func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ["name": value.name, "count": String(value.count)]
|
||||
}
|
||||
|
||||
func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard
|
||||
let object = object,
|
||||
let name = object["name"],
|
||||
let count = UInt(object["count"] ?? "0")
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value(name: name, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
private let fixtureCustomCollection = Item(name: "Apple", count: 10)
|
||||
private let fixtureCustomCollection1 = Item(name: "Banana", count: 20)
|
||||
private let fixtureCustomCollection2 = Item(name: "Grape", count: 30)
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let collectionCustomElement = Key<Bag<Item>>("collectionCustomElement", default: .init(items: [fixtureCustomCollection]))
|
||||
fileprivate static let collectionCustomElementArray = Key<[Bag<Item>]>("collectionCustomElementArray", default: [.init(items: [fixtureCustomCollection])])
|
||||
fileprivate static let collectionCustomElementDictionary = Key<[String: Bag<Item>]>("collectionCustomElementDictionary", default: ["0": .init(items: [fixtureCustomCollection])])
|
||||
}
|
||||
|
||||
final class DefaultsCollectionCustomElementTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<Bag<Item>>("independentCollectionCustomElementKey", default: .init(items: [fixtureCustomCollection]))
|
||||
Defaults[key].insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[key].insert(element: fixtureCustomCollection2, at: 2)
|
||||
XCTAssertEqual(Defaults[key][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key][1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key][2], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<Bag<Item>?>("independentCollectionCustomElementOptionalKey")
|
||||
Defaults[key] = .init(items: [fixtureCustomCollection])
|
||||
Defaults[key]?.insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[key]?.insert(element: fixtureCustomCollection2, at: 2)
|
||||
XCTAssertEqual(Defaults[key]?[0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key]?[1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key]?[2], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[Bag<Item>]>("independentCollectionCustomElementArrayKey", default: [.init(items: [fixtureCustomCollection])])
|
||||
Defaults[key][0].insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[key].append(.init(items: [fixtureCustomCollection2]))
|
||||
XCTAssertEqual(Defaults[key][0][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key][0][1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key][1][0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[Bag<Item>]?>("independentCollectionCustomElementArrayOptionalKey")
|
||||
Defaults[key] = [.init(items: [fixtureCustomCollection])]
|
||||
Defaults[key]?[0].insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[key]?.append(Bag(items: [fixtureCustomCollection2]))
|
||||
XCTAssertEqual(Defaults[key]?[0][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key]?[0][1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key]?[1][0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[Bag<Item>]]>("independentCollectionCustomElementNestedArrayKey", default: [[.init(items: [fixtureCustomCollection])]])
|
||||
Defaults[key][0][0].insert(element: fixtureCustomCollection, at: 1)
|
||||
Defaults[key][0].append(.init(items: [fixtureCustomCollection1]))
|
||||
Defaults[key].append([.init(items: [fixtureCustomCollection2])])
|
||||
XCTAssertEqual(Defaults[key][0][0][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key][0][0][1], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key][0][1][0], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key][1][0][0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: Bag<Item>]]>("independentCollectionCustomElementArrayDictionaryKey", default: [["0": .init(items: [fixtureCustomCollection])]])
|
||||
Defaults[key][0]["0"]?.insert(element: fixtureCustomCollection, at: 1)
|
||||
Defaults[key][0]["1"] = .init(items: [fixtureCustomCollection1])
|
||||
Defaults[key].append(["0": .init(items: [fixtureCustomCollection2])])
|
||||
XCTAssertEqual(Defaults[key][0]["0"]?[0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key][0]["0"]?[1], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key][0]["1"]?[0], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key][1]["0"]?[0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: Bag<Item>]>("independentCollectionCustomElementDictionaryKey", default: ["0": .init(items: [fixtureCustomCollection])])
|
||||
Defaults[key]["0"]?.insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[key]["1"] = .init(items: [fixtureCustomCollection2])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: Bag<Item>]?>("independentCollectionCustomElementDictionaryOptionalKey")
|
||||
Defaults[key] = ["0": .init(items: [fixtureCustomCollection])]
|
||||
Defaults[key]?["0"]?.insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[key]?["1"] = .init(items: [fixtureCustomCollection2])
|
||||
XCTAssertEqual(Defaults[key]?["0"]?[0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key]?["0"]?[1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key]?["1"]?[0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [Bag<Item>]]>("independentCollectionCustomElementDictionaryArrayKey", default: ["0": [.init(items: [fixtureCustomCollection])]])
|
||||
Defaults[key]["0"]?[0].insert(element: fixtureCustomCollection, at: 1)
|
||||
Defaults[key]["0"]?.append(.init(items: [fixtureCustomCollection1]))
|
||||
Defaults[key]["1"] = [.init(items: [fixtureCustomCollection2])]
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0][1], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1][0], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0][0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
Defaults[.collectionCustomElement].insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[.collectionCustomElement].insert(element: fixtureCustomCollection2, at: 2)
|
||||
XCTAssertEqual(Defaults[.collectionCustomElement][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[.collectionCustomElement][1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[.collectionCustomElement][2], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
Defaults[.collectionCustomElementArray][0].insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[.collectionCustomElementArray].append(.init(items: [fixtureCustomCollection2]))
|
||||
XCTAssertEqual(Defaults[.collectionCustomElementArray][0][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[.collectionCustomElementArray][0][1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[.collectionCustomElementArray][1][0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
Defaults[.collectionCustomElementDictionary]["0"]?.insert(element: fixtureCustomCollection1, at: 1)
|
||||
Defaults[.collectionCustomElementDictionary]["1"] = .init(items: [fixtureCustomCollection2])
|
||||
XCTAssertEqual(Defaults[.collectionCustomElementDictionary]["0"]?[0], fixtureCustomCollection)
|
||||
XCTAssertEqual(Defaults[.collectionCustomElementDictionary]["0"]?[1], fixtureCustomCollection1)
|
||||
XCTAssertEqual(Defaults[.collectionCustomElementDictionary]["1"]?[0], fixtureCustomCollection2)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<Bag<Item>>("observeCollectionCustomElementKeyCombine", default: .init(items: [fixtureCustomCollection]))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, fixtureCustomCollection)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(element: fixtureCustomCollection1, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<Bag<Item>?>("observeCollectionCustomElementOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(Item?, Item?)] = [(nil, fixtureCustomCollection), (fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0?[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1?[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .init(items: [fixtureCustomCollection])
|
||||
Defaults[key]?.insert(element: fixtureCustomCollection1, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[Bag<Item>]>("observeCollectionCustomElementArrayKeyCombine", default: [.init(items: [fixtureCustomCollection])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, fixtureCustomCollection)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0][0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0][0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0].insert(element: fixtureCustomCollection1, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: Bag<Item>]>("observeCollectionCustomElementDictionaryKeyCombine", default: ["0": .init(items: [fixtureCustomCollection])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, fixtureCustomCollection)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"]?[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"]?[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"]?.insert(element: fixtureCustomCollection1, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<Bag<Item>>("observeCollectionCustomElementKey", default: .init(items: [fixtureCustomCollection]))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0], fixtureCustomCollection)
|
||||
XCTAssertEqual(change.newValue[0], fixtureCustomCollection1)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(element: fixtureCustomCollection1, at: 0)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<Bag<Item>?>("observeCollectionCustomElementOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue?[0], fixtureCustomCollection)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .init(items: [fixtureCustomCollection])
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[Bag<Item>]>("observeCollectionCustomElementArrayKey", default: [.init(items: [fixtureCustomCollection])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0][0], fixtureCustomCollection)
|
||||
XCTAssertEqual(change.newValue[0][0], fixtureCustomCollection1)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0].insert(element: fixtureCustomCollection1, at: 0)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: Bag<Item>]>("observeCollectionCustomElementArrayKey", default: ["0": .init(items: [fixtureCustomCollection])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"]?[0], fixtureCustomCollection)
|
||||
XCTAssertEqual(change.newValue["0"]?[0], fixtureCustomCollection1)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"]?.insert(element: fixtureCustomCollection1, at: 0)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
import Defaults
|
||||
|
||||
struct Bag<Element: Defaults.Serializable>: Collection {
|
||||
var items: [Element]
|
||||
|
||||
init(items: [Element]) {
|
||||
self.items = items
|
||||
}
|
||||
|
||||
var startIndex: Int {
|
||||
items.startIndex
|
||||
}
|
||||
|
||||
var endIndex: Int {
|
||||
items.endIndex
|
||||
}
|
||||
|
||||
mutating func insert(element: Element, at: Int) {
|
||||
items.insert(element, at: at)
|
||||
}
|
||||
|
||||
func index(after index: Int) -> Int {
|
||||
items.index(after: index)
|
||||
}
|
||||
|
||||
subscript(position: Int) -> Element {
|
||||
items[position]
|
||||
}
|
||||
}
|
||||
|
||||
extension Bag: Defaults.CollectionSerializable {
|
||||
init(_ elements: [Element]) {
|
||||
self.items = elements
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let fixtureCollection = ["Juice", "Apple", "Banana"]
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let collection = Key<Bag<String>>("collection", default: Bag(items: fixtureCollection))
|
||||
fileprivate static let collectionArray = Key<[Bag<String>]>("collectionArray", default: [Bag(items: fixtureCollection)])
|
||||
fileprivate static let collectionDictionary = Key<[String: Bag<String>]>("collectionDictionary", default: ["0": Bag(items: fixtureCollection)])
|
||||
}
|
||||
|
||||
final class DefaultsCollectionTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<Bag<String>>("independentCollectionKey", default: Bag(items: fixtureCollection))
|
||||
Defaults[key].insert(element: "123", at: 0)
|
||||
XCTAssertEqual(Defaults[key][0], "123")
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<Bag<String>?>("independentCollectionOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = Bag(items: [])
|
||||
Defaults[key]?.insert(element: fixtureCollection[0], at: 0)
|
||||
XCTAssertEqual(Defaults[key]?[0], fixtureCollection[0])
|
||||
Defaults[key]?.insert(element: fixtureCollection[1], at: 1)
|
||||
XCTAssertEqual(Defaults[key]?[1], fixtureCollection[1])
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[Bag<String>]>("independentCollectionArrayKey", default: [Bag(items: [fixtureCollection[0]])])
|
||||
Defaults[key].append(Bag(items: [fixtureCollection[1]]))
|
||||
XCTAssertEqual(Defaults[key][1][0], fixtureCollection[1])
|
||||
Defaults[key][0].insert(element: fixtureCollection[2], at: 1)
|
||||
XCTAssertEqual(Defaults[key][0][1], fixtureCollection[2])
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[Bag<String>]?>("independentCollectionArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [Bag(items: [fixtureCollection[0]])]
|
||||
Defaults[key]?.append(Bag(items: [fixtureCollection[1]]))
|
||||
XCTAssertEqual(Defaults[key]?[1][0], fixtureCollection[1])
|
||||
Defaults[key]?[0].insert(element: fixtureCollection[2], at: 1)
|
||||
XCTAssertEqual(Defaults[key]?[0][1], fixtureCollection[2])
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[Bag<String>]]>("independentCollectionNestedArrayKey", default: [[Bag(items: [fixtureCollection[0]])]])
|
||||
Defaults[key][0].append(Bag(items: [fixtureCollection[1]]))
|
||||
Defaults[key].append([Bag(items: [fixtureCollection[2]])])
|
||||
XCTAssertEqual(Defaults[key][0][0][0], fixtureCollection[0])
|
||||
XCTAssertEqual(Defaults[key][0][1][0], fixtureCollection[1])
|
||||
XCTAssertEqual(Defaults[key][1][0][0], fixtureCollection[2])
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: Bag<String>]]>("independentCollectionArrayDictionaryKey", default: [["0": Bag(items: [fixtureCollection[0]])]])
|
||||
Defaults[key][0]["1"] = Bag(items: [fixtureCollection[1]])
|
||||
Defaults[key].append(["0": Bag(items: [fixtureCollection[2]])])
|
||||
XCTAssertEqual(Defaults[key][0]["0"]?[0], fixtureCollection[0])
|
||||
XCTAssertEqual(Defaults[key][0]["1"]?[0], fixtureCollection[1])
|
||||
XCTAssertEqual(Defaults[key][1]["0"]?[0], fixtureCollection[2])
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: Bag<String>]>("independentCollectionDictionaryKey", default: ["0": Bag(items: [fixtureCollection[0]])])
|
||||
Defaults[key]["0"]?.insert(element: fixtureCollection[1], at: 1)
|
||||
Defaults[key]["1"] = Bag(items: [fixtureCollection[2]])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], fixtureCollection[0])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCollection[1])
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0], fixtureCollection[2])
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: Bag<String>]?>("independentCollectionDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": Bag(items: [fixtureCollection[0]])]
|
||||
Defaults[key]?["0"]?.insert(element: fixtureCollection[1], at: 1)
|
||||
Defaults[key]?["1"] = Bag(items: [fixtureCollection[2]])
|
||||
XCTAssertEqual(Defaults[key]?["0"]?[0], fixtureCollection[0])
|
||||
XCTAssertEqual(Defaults[key]?["0"]?[1], fixtureCollection[1])
|
||||
XCTAssertEqual(Defaults[key]?["1"]?[0], fixtureCollection[2])
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [Bag<String>]]>("independentCollectionDictionaryArrayKey", default: ["0": [Bag(items: [fixtureCollection[0]])]])
|
||||
Defaults[key]["0"]?[0].insert(element: fixtureCollection[1], at: 1)
|
||||
Defaults[key]["1"] = [Bag(items: [fixtureCollection[2]])]
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0][0], fixtureCollection[0])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0][1], fixtureCollection[1])
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0][0], fixtureCollection[2])
|
||||
}
|
||||
|
||||
func testType() {
|
||||
Defaults[.collection].insert(element: "123", at: 0)
|
||||
XCTAssertEqual(Defaults[.collection][0], "123")
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
Defaults[.collectionArray].append(Bag(items: [fixtureCollection[0]]))
|
||||
Defaults[.collectionArray][0].insert(element: "123", at: 0)
|
||||
XCTAssertEqual(Defaults[.collectionArray][0][0], "123")
|
||||
XCTAssertEqual(Defaults[.collectionArray][1][0], fixtureCollection[0])
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
Defaults[.collectionDictionary]["1"] = Bag(items: [fixtureCollection[0]])
|
||||
Defaults[.collectionDictionary]["0"]?.insert(element: "123", at: 0)
|
||||
XCTAssertEqual(Defaults[.collectionDictionary]["0"]?[0], "123")
|
||||
XCTAssertEqual(Defaults[.collectionDictionary]["1"]?[0], fixtureCollection[0])
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<Bag<String>>("observeCollectionKeyCombine", default: .init(items: fixtureCollection))
|
||||
let item = "Grape"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCollection[0], item), (item, fixtureCollection[0])].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(element: item, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<Bag<String>?>("observeCollectionOptionalKeyCombine")
|
||||
let item = "Grape"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(String?, String?)] = [(nil, fixtureCollection[0]), (fixtureCollection[0], item), (item, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0?[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1?[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = Bag(items: fixtureCollection)
|
||||
Defaults[key]?.insert(element: item, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[Bag<String>]>("observeCollectionArrayKeyCombine", default: [.init(items: fixtureCollection)])
|
||||
let item = "Grape"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCollection[0], item), (item, fixtureCollection[0])].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0][0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0][0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0].insert(element: item, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: Bag<String>]>("observeCollectionArrayKeyCombine", default: ["0": .init(items: fixtureCollection)])
|
||||
let item = "Grape"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCollection[0], item), (item, fixtureCollection[0])].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"]?[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"]?[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"]?.insert(element: item, at: 0)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<Bag<String>>("observeCollectionKey", default: .init(items: fixtureCollection))
|
||||
let item = "Grape"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0], fixtureCollection[0])
|
||||
XCTAssertEqual(change.newValue[0], item)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(element: item, at: 0)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<Bag<String>?>("observeCollectionOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue?[0], fixtureCollection[0])
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .init(items: fixtureCollection)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[Bag<String>]>("observeCollectionArrayKey", default: [.init(items: fixtureCollection)])
|
||||
let item = "Grape"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0][0], fixtureCollection[0])
|
||||
XCTAssertEqual(change.newValue[0][0], item)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0].insert(element: item, at: 0)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: Bag<String>]>("observeCollectionDictionaryKey", default: ["0": .init(items: fixtureCollection)])
|
||||
let item = "Grape"
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"]?[0], fixtureCollection[0])
|
||||
XCTAssertEqual(change.newValue["0"]?[0], item)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"]?.insert(element: item, at: 0)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
|
||||
public struct User: Hashable, Equatable {
|
||||
var username: String
|
||||
var password: String
|
||||
}
|
||||
|
||||
extension User: Defaults.Serializable {
|
||||
public static let bridge = DefaultsUserBridge()
|
||||
}
|
||||
|
||||
public final class DefaultsUserBridge: Defaults.Bridge {
|
||||
public typealias Value = User
|
||||
public typealias Serializable = [String: String]
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ["username": value.username, "password": value.password]
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard
|
||||
let object = object,
|
||||
let username = object["username"],
|
||||
let password = object["password"]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return User(username: username, password: password)
|
||||
}
|
||||
}
|
||||
|
||||
private let fixtureCustomBridge = User(username: "hank121314", password: "123456")
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let customBridge = Key<User>("customBridge", default: fixtureCustomBridge)
|
||||
fileprivate static let customBridgeArray = Key<[User]>("array_customBridge", default: [fixtureCustomBridge])
|
||||
fileprivate static let customBridgeDictionary = Key<[String: User]>("dictionary_customBridge", default: ["0": fixtureCustomBridge])
|
||||
}
|
||||
|
||||
|
||||
final class DefaultsCustomBridge: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<User>("independentCustomBridgeKey", default: fixtureCustomBridge)
|
||||
XCTAssertEqual(Defaults[key], fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[key] = newUser
|
||||
XCTAssertEqual(Defaults[key], newUser)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<User?>("independentCustomBridgeOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = fixtureCustomBridge
|
||||
XCTAssertEqual(Defaults[key], fixtureCustomBridge)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let user = User(username: "hank121314", password: "123456")
|
||||
let key = Defaults.Key<[User]>("independentCustomBridgeArrayKey", default: [user])
|
||||
XCTAssertEqual(Defaults[key][0], user)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[key][0] = newUser
|
||||
XCTAssertEqual(Defaults[key][0], newUser)
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[User]?>("independentCustomBridgeArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
let newUser = User(username: "hank121314", password: "123456")
|
||||
Defaults[key] = [newUser]
|
||||
XCTAssertEqual(Defaults[key]?[0], newUser)
|
||||
Defaults[key] = nil
|
||||
XCTAssertNil(Defaults[key])
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[User]]>("independentCustomBridgeNestedArrayKey", default: [[fixtureCustomBridge], [fixtureCustomBridge]])
|
||||
XCTAssertEqual(Defaults[key][0][0].username, fixtureCustomBridge.username)
|
||||
let newUsername = "John"
|
||||
let newPassword = "7891011"
|
||||
Defaults[key][0][0] = User(username: newUsername, password: newPassword)
|
||||
XCTAssertEqual(Defaults[key][0][0].username, newUsername)
|
||||
XCTAssertEqual(Defaults[key][0][0].password, newPassword)
|
||||
XCTAssertEqual(Defaults[key][1][0].username, fixtureCustomBridge.username)
|
||||
XCTAssertEqual(Defaults[key][1][0].password, fixtureCustomBridge.password)
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: User]]>("independentCustomBridgeArrayDictionaryKey", default: [["0": fixtureCustomBridge], ["0": fixtureCustomBridge]])
|
||||
XCTAssertEqual(Defaults[key][0]["0"]?.username, fixtureCustomBridge.username)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[key][0]["0"] = newUser
|
||||
XCTAssertEqual(Defaults[key][0]["0"], newUser)
|
||||
XCTAssertEqual(Defaults[key][1]["0"], fixtureCustomBridge)
|
||||
}
|
||||
|
||||
func testSetKey() {
|
||||
let key = Defaults.Key<Set<User>>("independentCustomBridgeSetKey", default: [fixtureCustomBridge])
|
||||
XCTAssertEqual(Defaults[key].first, fixtureCustomBridge)
|
||||
Defaults[key].insert(fixtureCustomBridge)
|
||||
XCTAssertEqual(Defaults[key].count, 1)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[key].insert(newUser)
|
||||
XCTAssertTrue(Defaults[key].contains(newUser))
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: User]>("independentCustomBridgeDictionaryKey", default: ["0": fixtureCustomBridge])
|
||||
XCTAssertEqual(Defaults[key]["0"], fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[key]["0"] = newUser
|
||||
XCTAssertEqual(Defaults[key]["0"], newUser)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: User]?>("independentCustomBridgeDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": fixtureCustomBridge]
|
||||
XCTAssertEqual(Defaults[key]?["0"], fixtureCustomBridge)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [User]]>("independentCustomBridgeDictionaryArrayKey", default: ["0": [fixtureCustomBridge]])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[key]["0"]?[0] = newUser
|
||||
Defaults[key]["0"]?.append(fixtureCustomBridge)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], newUser)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], newUser)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomBridge)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomBridge)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssertEqual(Defaults[.customBridge], fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[.customBridge] = newUser
|
||||
XCTAssertEqual(Defaults[.customBridge], newUser)
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
XCTAssertEqual(Defaults[.customBridgeArray][0], fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[.customBridgeArray][0] = newUser
|
||||
XCTAssertEqual(Defaults[.customBridgeArray][0], newUser)
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
XCTAssertEqual(Defaults[.customBridgeDictionary]["0"], fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
Defaults[.customBridgeDictionary]["0"] = newUser
|
||||
XCTAssertEqual(Defaults[.customBridgeDictionary]["0"], newUser)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<User>("observeCustomBridgeKeyCombine", default: fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCustomBridge, newUser), (newUser, fixtureCustomBridge)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = newUser
|
||||
Defaults[key] = fixtureCustomBridge
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<User?>("observeCustomBridgeOptionalKeyCombine")
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(User?, User?)] = [(nil, fixtureCustomBridge), (fixtureCustomBridge, newUser), (newUser, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureCustomBridge
|
||||
Defaults[key] = newUser
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[User]>("observeCustomBridgeArrayKeyCombine", default: [fixtureCustomBridge])
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [([fixtureCustomBridge], [newUser]), ([newUser], [newUser, fixtureCustomBridge])].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = newUser
|
||||
Defaults[key].append(fixtureCustomBridge)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryCombine() {
|
||||
let key = Defaults.Key<[String: User]>("observeCustomBridgeDictionaryKeyCombine", default: ["0": fixtureCustomBridge])
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureCustomBridge, newUser), (newUser, fixtureCustomBridge)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = newUser
|
||||
Defaults[key]["0"] = fixtureCustomBridge
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<User>("observeCustomBridgeKey", default: fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, fixtureCustomBridge)
|
||||
XCTAssertEqual(change.newValue, newUser)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = newUser
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<User?>("observeCustomBridgeOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue, fixtureCustomBridge)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureCustomBridge
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[User]>("observeCustomBridgeArrayKey", default: [fixtureCustomBridge])
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0], fixtureCustomBridge)
|
||||
XCTAssertEqual(change.newValue[0], newUser)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = newUser
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: User]>("observeCustomBridgeDictionaryKey", default: ["0": fixtureCustomBridge])
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"], fixtureCustomBridge)
|
||||
XCTAssertEqual(change.newValue["0"], newUser)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = newUser
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
|
||||
private let fixtureDictionary = ["0": "Hank"]
|
||||
|
||||
private let fixtureArray = ["Hank", "Chen"]
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let dictionary = Key<[String: String]>("dictionary", default: fixtureDictionary)
|
||||
}
|
||||
|
||||
final class DefaultsDictionaryTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<[String: String]>("independentDictionaryStringKey", default: fixtureDictionary)
|
||||
XCTAssertEqual(Defaults[key]["0"], fixtureDictionary["0"])
|
||||
let newValue = "John"
|
||||
Defaults[key]["0"] = newValue
|
||||
XCTAssertEqual(Defaults[key]["0"], newValue)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<[String: String]?>("independentDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = fixtureDictionary
|
||||
XCTAssertEqual(Defaults[key]?["0"], fixtureDictionary["0"])
|
||||
Defaults[key] = nil
|
||||
XCTAssertNil(Defaults[key])
|
||||
let newValue = ["0": "Chen"]
|
||||
Defaults[key] = newValue
|
||||
XCTAssertEqual(Defaults[key]?["0"], newValue["0"])
|
||||
}
|
||||
|
||||
func testNestedKey() {
|
||||
let key = Defaults.Key<[String: [String: String]]>("independentDictionaryNestedKey", default: ["0": fixtureDictionary])
|
||||
XCTAssertEqual(Defaults[key]["0"]?["0"], "Hank")
|
||||
let newName = "Chen"
|
||||
Defaults[key]["0"]?["0"] = newName
|
||||
XCTAssertEqual(Defaults[key]["0"]?["0"], newName)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[String: [String]]>("independentDictionaryArrayKey", default: ["0": fixtureArray])
|
||||
XCTAssertEqual(Defaults[key]["0"], fixtureArray)
|
||||
let newName = "Chen"
|
||||
Defaults[key]["0"]?[0] = newName
|
||||
XCTAssertEqual(Defaults[key]["0"], [newName, fixtureArray[1]])
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssertEqual(Defaults[.dictionary]["0"], fixtureDictionary["0"])
|
||||
let newName = "Chen"
|
||||
Defaults[.dictionary]["0"] = newName
|
||||
XCTAssertEqual(Defaults[.dictionary]["0"], newName)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<[String: String]>("observeDictionaryKeyCombine", default: fixtureDictionary)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
let newName = "John"
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureDictionary["0"]!, newName), (newName, fixtureDictionary["0"]!)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = newName
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<[String: String]?>("observeDictionaryOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
let newName = ["0": "John"]
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
// swiftlint:disable discouraged_optional_collection
|
||||
let expectedValues: [([String: String]?, [String: String]?)] = [(nil, fixtureDictionary), (fixtureDictionary, newName), (newName, nil)]
|
||||
|
||||
let cancellable = publisher.sink { actualValues in
|
||||
for (expected, actual) in zip(expectedValues, actualValues) {
|
||||
XCTAssertEqual(expected.0, actual.0)
|
||||
XCTAssertEqual(expected.1, actual.1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureDictionary
|
||||
Defaults[key] = newName
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<[String: String]>("observeDictionaryKey", default: fixtureDictionary)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
let newName = "John"
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, fixtureDictionary)
|
||||
XCTAssertEqual(change.newValue["1"], newName)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = newName
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<[String: String]?>("observeDictionaryOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue!, fixtureDictionary)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureDictionary
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
|
||||
private enum FixtureEnum: String, Defaults.Serializable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let `enum` = Key<FixtureEnum>("enum", default: .tenMinutes)
|
||||
fileprivate static let enumArray = Key<[FixtureEnum]>("array_enum", default: [.tenMinutes])
|
||||
fileprivate static let enumDictionary = Key<[String: FixtureEnum]>("dictionary_enum", default: ["0": .tenMinutes])
|
||||
}
|
||||
|
||||
final class DefaultsEnumTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<FixtureEnum>("independentEnumKey", default: .tenMinutes)
|
||||
XCTAssertEqual(Defaults[key], .tenMinutes)
|
||||
Defaults[key] = .halfHour
|
||||
XCTAssertEqual(Defaults[key], .halfHour)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<FixtureEnum?>("independentEnumOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = .tenMinutes
|
||||
XCTAssertEqual(Defaults[key], .tenMinutes)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[FixtureEnum]>("independentEnumArrayKey", default: [.tenMinutes])
|
||||
XCTAssertEqual(Defaults[key][0], .tenMinutes)
|
||||
Defaults[key].append(.halfHour)
|
||||
XCTAssertEqual(Defaults[key][0], .tenMinutes)
|
||||
XCTAssertEqual(Defaults[key][1], .halfHour)
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[FixtureEnum]?>("independentEnumArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [.tenMinutes]
|
||||
Defaults[key]?.append(.halfHour)
|
||||
XCTAssertEqual(Defaults[key]?[0], .tenMinutes)
|
||||
XCTAssertEqual(Defaults[key]?[1], .halfHour)
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[FixtureEnum]]>("independentEnumNestedArrayKey", default: [[.tenMinutes]])
|
||||
XCTAssertEqual(Defaults[key][0][0], .tenMinutes)
|
||||
Defaults[key][0].append(.halfHour)
|
||||
Defaults[key].append([.oneHour])
|
||||
XCTAssertEqual(Defaults[key][0][1], .halfHour)
|
||||
XCTAssertEqual(Defaults[key][1][0], .oneHour)
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: FixtureEnum]]>("independentEnumArrayDictionaryKey", default: [["0": .tenMinutes]])
|
||||
XCTAssertEqual(Defaults[key][0]["0"], .tenMinutes)
|
||||
Defaults[key][0]["1"] = .halfHour
|
||||
Defaults[key].append(["0": .oneHour])
|
||||
XCTAssertEqual(Defaults[key][0]["1"], .halfHour)
|
||||
XCTAssertEqual(Defaults[key][1]["0"], .oneHour)
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: FixtureEnum]>("independentEnumDictionaryKey", default: ["0": .tenMinutes])
|
||||
XCTAssertEqual(Defaults[key]["0"], .tenMinutes)
|
||||
Defaults[key]["1"] = .halfHour
|
||||
XCTAssertEqual(Defaults[key]["0"], .tenMinutes)
|
||||
XCTAssertEqual(Defaults[key]["1"], .halfHour)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: FixtureEnum]?>("independentEnumDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": .tenMinutes]
|
||||
XCTAssertEqual(Defaults[key]?["0"], .tenMinutes)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [FixtureEnum]]>("independentEnumDictionaryKey", default: ["0": [.tenMinutes]])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], .tenMinutes)
|
||||
Defaults[key]["0"]?.append(.halfHour)
|
||||
Defaults[key]["1"] = [.oneHour]
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], .halfHour)
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0], .oneHour)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssertEqual(Defaults[.enum], .tenMinutes)
|
||||
Defaults[.enum] = .halfHour
|
||||
XCTAssertEqual(Defaults[.enum], .halfHour)
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
XCTAssertEqual(Defaults[.enumArray][0], .tenMinutes)
|
||||
Defaults[.enumArray][0] = .oneHour
|
||||
XCTAssertEqual(Defaults[.enumArray][0], .oneHour)
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
XCTAssertEqual(Defaults[.enumDictionary]["0"], .tenMinutes)
|
||||
Defaults[.enumDictionary]["0"] = .halfHour
|
||||
XCTAssertEqual(Defaults[.enumDictionary]["0"], .halfHour)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<FixtureEnum>("observeEnumKeyCombine", default: .tenMinutes)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(FixtureEnum, FixtureEnum)] = [(.tenMinutes, .halfHour), (.halfHour, .oneHour), (.oneHour, .tenMinutes)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .tenMinutes
|
||||
Defaults[key] = .halfHour
|
||||
Defaults[key] = .oneHour
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<FixtureEnum?>("observeEnumOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(4)
|
||||
|
||||
let expectedValue: [(FixtureEnum?, FixtureEnum?)] = [(nil, .tenMinutes), (.tenMinutes, .halfHour), (.halfHour, .oneHour), (.oneHour, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .tenMinutes
|
||||
Defaults[key] = .halfHour
|
||||
Defaults[key] = .oneHour
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[FixtureEnum]>("observeEnumArrayKeyCombine", default: [.tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(FixtureEnum, FixtureEnum)] = [(.tenMinutes, .halfHour), (.halfHour, .oneHour)]
|
||||
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = .halfHour
|
||||
Defaults[key][0] = .oneHour
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: FixtureEnum]>("observeEnumDictionaryKeyCombine", default: ["0": .tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(FixtureEnum, FixtureEnum)] = [(.tenMinutes, .halfHour), (.halfHour, .oneHour)]
|
||||
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = .halfHour
|
||||
Defaults[key]["0"] = .oneHour
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<FixtureEnum>("observeEnumKey", default: .tenMinutes)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, .tenMinutes)
|
||||
XCTAssertEqual(change.newValue, .halfHour)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .halfHour
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<FixtureEnum?>("observeEnumOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue, .tenMinutes)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .tenMinutes
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[FixtureEnum]>("observeEnumArrayKey", default: [.tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0], .tenMinutes)
|
||||
XCTAssertEqual(change.newValue[1], .halfHour)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].append(.halfHour)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: FixtureEnum]>("observeEnumDictionaryKey", default: ["0": .tenMinutes])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"], .tenMinutes)
|
||||
XCTAssertEqual(change.newValue["1"], .halfHour)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = .halfHour
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,306 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
import AppKit
|
||||
|
||||
private let fixtureColor = NSColor(red: CGFloat(103) / CGFloat(0xFF), green: CGFloat(132) / CGFloat(0xFF), blue: CGFloat(255) / CGFloat(0xFF), alpha: 1)
|
||||
private let fixtureColor1 = NSColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(241) / CGFloat(0xFF), blue: CGFloat(180) / CGFloat(0xFF), alpha: 1)
|
||||
private let fixtureColor2 = NSColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(180) / CGFloat(0xFF), blue: CGFloat(194) / CGFloat(0xFF), alpha: 1)
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let color = Defaults.Key<NSColor>("NSColor", default: fixtureColor)
|
||||
fileprivate static let colorArray = Defaults.Key<[NSColor]>("NSColorArray", default: [fixtureColor])
|
||||
fileprivate static let colorDictionary = Defaults.Key<[String: NSColor]>("NSColorArray", default: ["0": fixtureColor])
|
||||
}
|
||||
|
||||
final class DefaultsNSColorTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<NSColor>("independentNSColorKey", default: fixtureColor)
|
||||
XCTAssertTrue(Defaults[key].isEqual(fixtureColor))
|
||||
Defaults[key] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[key].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<NSColor?>("independentNSColorOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = fixtureColor
|
||||
XCTAssertTrue(Defaults[key]?.isEqual(fixtureColor) ?? false)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[NSColor]>("independentNSColorArrayKey", default: [fixtureColor])
|
||||
XCTAssertTrue(Defaults[key][0].isEqual(fixtureColor))
|
||||
Defaults[key].append(fixtureColor1)
|
||||
XCTAssertTrue(Defaults[key][1].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[NSColor]?>("independentNSColorOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [fixtureColor]
|
||||
Defaults[key]?.append(fixtureColor1)
|
||||
XCTAssertTrue(Defaults[key]?[0].isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(Defaults[key]?[1].isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[NSColor]]>("independentNSColorNestedArrayKey", default: [[fixtureColor]])
|
||||
XCTAssertTrue(Defaults[key][0][0].isEqual(fixtureColor))
|
||||
Defaults[key][0].append(fixtureColor1)
|
||||
Defaults[key].append([fixtureColor2])
|
||||
XCTAssertTrue(Defaults[key][0][1].isEqual(fixtureColor1))
|
||||
XCTAssertTrue(Defaults[key][1][0].isEqual(fixtureColor2))
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: NSColor]]>("independentNSColorArrayDictionaryKey", default: [["0": fixtureColor]])
|
||||
XCTAssertTrue(Defaults[key][0]["0"]?.isEqual(fixtureColor) ?? false)
|
||||
Defaults[key][0]["1"] = fixtureColor1
|
||||
Defaults[key].append(["0": fixtureColor2])
|
||||
XCTAssertTrue(Defaults[key][0]["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
XCTAssertTrue(Defaults[key][1]["0"]?.isEqual(fixtureColor2) ?? false)
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: NSColor]>("independentNSColorDictionaryKey", default: ["0": fixtureColor])
|
||||
XCTAssertTrue(Defaults[key]["0"]?.isEqual(fixtureColor) ?? false)
|
||||
Defaults[key]["1"] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[key]["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: NSColor]?>("independentNSColorDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": fixtureColor]
|
||||
Defaults[key]?["1"] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[key]?["0"]?.isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(Defaults[key]?["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [NSColor]]>("independentNSColorDictionaryArrayKey", default: ["0": [fixtureColor]])
|
||||
XCTAssertTrue(Defaults[key]["0"]?[0].isEqual(fixtureColor) ?? false)
|
||||
Defaults[key]["0"]?.append(fixtureColor1)
|
||||
Defaults[key]["1"] = [fixtureColor2]
|
||||
XCTAssertTrue(Defaults[key]["0"]?[1].isEqual(fixtureColor1) ?? false)
|
||||
XCTAssertTrue(Defaults[key]["1"]?[0].isEqual(fixtureColor2) ?? false)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssert(Defaults[.color].isEqual(fixtureColor))
|
||||
Defaults[.color] = fixtureColor1
|
||||
XCTAssert(Defaults[.color].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor))
|
||||
Defaults[.colorArray][0] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor) ?? false)
|
||||
Defaults[.colorDictionary]["0"] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<NSColor>("observeNSColorKeyCombine", default: fixtureColor)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
|
||||
XCTAssertTrue(expected.0.isEqual(tuples[index].0))
|
||||
XCTAssertTrue(expected.1.isEqual(tuples[index].1))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<NSColor?>("observeNSColorOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(NSColor?, NSColor?)] = [(nil, fixtureColor), (fixtureColor, fixtureColor1), (fixtureColor1, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
guard let oldValue = expected.0 else {
|
||||
XCTAssertNil(tuples[index].0)
|
||||
continue
|
||||
}
|
||||
guard let newValue = expected.1 else {
|
||||
XCTAssertNil(tuples[index].1)
|
||||
continue
|
||||
}
|
||||
XCTAssertTrue(oldValue.isEqual(tuples[index].0))
|
||||
XCTAssertTrue(newValue.isEqual(tuples[index].1))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor
|
||||
Defaults[key] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[NSColor]>("observeNSColorArrayKeyCombine", default: [fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
|
||||
XCTAssertTrue(expected.0.isEqual(tuples[index].0[0]))
|
||||
XCTAssertTrue(expected.1.isEqual(tuples[index].1[0]))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: NSColor]>("observeNSColorDictionaryKeyCombine", default: ["0": fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
|
||||
XCTAssertTrue(expected.0.isEqual(tuples[index].0["0"]))
|
||||
XCTAssertTrue(expected.1.isEqual(tuples[index].1["0"]))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<NSColor>("observeNSColorKey", default: fixtureColor)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue.isEqual(fixtureColor))
|
||||
XCTAssertTrue(change.newValue.isEqual(fixtureColor1))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor1
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<NSColor?>("observeNSColorOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertTrue(change.newValue?.isEqual(fixtureColor) ?? false)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[NSColor]>("observeNSColorArrayKey", default: [fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue[0].isEqual(fixtureColor))
|
||||
XCTAssertTrue(change.newValue[0].isEqual(fixtureColor))
|
||||
XCTAssertTrue(change.newValue[1].isEqual(fixtureColor1))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].append(fixtureColor1)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: NSColor]>("observeNSColorDictionaryKey", default: ["0": fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue["0"]?.isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(change.newValue["0"]?.isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(change.newValue["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = fixtureColor1
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,474 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import Defaults
|
||||
import XCTest
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private final class ExamplePersistentHistory: NSPersistentHistoryToken, Defaults.Serializable {
|
||||
let value: String
|
||||
|
||||
init(value: String) {
|
||||
self.value = value
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
self.value = coder.decodeObject(forKey: "value") as! String
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func encode(with coder: NSCoder) {
|
||||
coder.encode(value, forKey: "value")
|
||||
}
|
||||
|
||||
override class var supportsSecureCoding: Bool { true }
|
||||
}
|
||||
|
||||
// NSSecureCoding
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
private let persistentHistoryValue = ExamplePersistentHistory(value: "ExampleToken")
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let persistentHistory = Key<ExamplePersistentHistory>("persistentHistory", default: persistentHistoryValue)
|
||||
fileprivate static let persistentHistoryArray = Key<[ExamplePersistentHistory]>("array_persistentHistory", default: [persistentHistoryValue])
|
||||
fileprivate static let persistentHistoryDictionary = Key<[String: ExamplePersistentHistory]>("dictionary_persistentHistory", default: ["0": persistentHistoryValue])
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
final class DefaultsNSSecureCodingTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory>("independentNSSecureCodingKey", default: persistentHistoryValue)
|
||||
XCTAssertEqual(Defaults[key].value, persistentHistoryValue.value)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
Defaults[key] = newPersistentHistory
|
||||
XCTAssertEqual(Defaults[key].value, newPersistentHistory.value)
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory?>("independentNSSecureCodingOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = persistentHistoryValue
|
||||
XCTAssertEqual(Defaults[key]?.value, persistentHistoryValue.value)
|
||||
Defaults[key] = nil
|
||||
XCTAssertNil(Defaults[key])
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
Defaults[key] = newPersistentHistory
|
||||
XCTAssertEqual(Defaults[key]?.value, newPersistentHistory.value)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[ExamplePersistentHistory]>("independentNSSecureCodingArrayKey", default: [persistentHistoryValue])
|
||||
XCTAssertEqual(Defaults[key][0].value, persistentHistoryValue.value)
|
||||
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
|
||||
Defaults[key].append(newPersistentHistory1)
|
||||
XCTAssertEqual(Defaults[key][1].value, newPersistentHistory1.value)
|
||||
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
|
||||
Defaults[key][1] = newPersistentHistory2
|
||||
XCTAssertEqual(Defaults[key][1].value, newPersistentHistory2.value)
|
||||
XCTAssertEqual(Defaults[key][0].value, persistentHistoryValue.value)
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[ExamplePersistentHistory]?>("independentNSSecureCodingArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [persistentHistoryValue]
|
||||
XCTAssertEqual(Defaults[key]?[0].value, persistentHistoryValue.value)
|
||||
Defaults[key] = nil
|
||||
XCTAssertNil(Defaults[key])
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[ExamplePersistentHistory]]>("independentNSSecureCodingNestedArrayKey", default: [[persistentHistoryValue]])
|
||||
XCTAssertEqual(Defaults[key][0][0].value, persistentHistoryValue.value)
|
||||
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
|
||||
Defaults[key][0].append(newPersistentHistory1)
|
||||
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
|
||||
Defaults[key].append([newPersistentHistory2])
|
||||
XCTAssertEqual(Defaults[key][0][1].value, newPersistentHistory1.value)
|
||||
XCTAssertEqual(Defaults[key][1][0].value, newPersistentHistory2.value)
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: ExamplePersistentHistory]]>("independentNSSecureCodingArrayDictionaryKey", default: [["0": persistentHistoryValue]])
|
||||
XCTAssertEqual(Defaults[key][0]["0"]?.value, persistentHistoryValue.value)
|
||||
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
|
||||
Defaults[key][0]["1"] = newPersistentHistory1
|
||||
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
|
||||
Defaults[key].append(["0": newPersistentHistory2])
|
||||
XCTAssertEqual(Defaults[key][0]["1"]?.value, newPersistentHistory1.value)
|
||||
XCTAssertEqual(Defaults[key][1]["0"]?.value, newPersistentHistory2.value)
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: ExamplePersistentHistory]>("independentNSSecureCodingDictionaryKey", default: ["0": persistentHistoryValue])
|
||||
XCTAssertEqual(Defaults[key]["0"]?.value, persistentHistoryValue.value)
|
||||
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
|
||||
Defaults[key]["1"] = newPersistentHistory1
|
||||
XCTAssertEqual(Defaults[key]["1"]?.value, newPersistentHistory1.value)
|
||||
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
|
||||
Defaults[key]["1"] = newPersistentHistory2
|
||||
XCTAssertEqual(Defaults[key]["1"]?.value, newPersistentHistory2.value)
|
||||
XCTAssertEqual(Defaults[key]["0"]?.value, persistentHistoryValue.value)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: ExamplePersistentHistory]?>("independentNSSecureCodingDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": persistentHistoryValue]
|
||||
XCTAssertEqual(Defaults[key]?["0"]?.value, persistentHistoryValue.value)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [ExamplePersistentHistory]]>("independentNSSecureCodingDictionaryArrayKey", default: ["0": [persistentHistoryValue]])
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0].value, persistentHistoryValue.value)
|
||||
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
|
||||
Defaults[key]["0"]?.append(newPersistentHistory1)
|
||||
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
|
||||
Defaults[key]["1"] = [newPersistentHistory2]
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1].value, newPersistentHistory1.value)
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0].value, newPersistentHistory2.value)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssertEqual(Defaults[.persistentHistory].value, persistentHistoryValue.value)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
Defaults[.persistentHistory] = newPersistentHistory
|
||||
XCTAssertEqual(Defaults[.persistentHistory].value, newPersistentHistory.value)
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
XCTAssertEqual(Defaults[.persistentHistoryArray][0].value, persistentHistoryValue.value)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
Defaults[.persistentHistoryArray][0] = newPersistentHistory
|
||||
XCTAssertEqual(Defaults[.persistentHistoryArray][0].value, newPersistentHistory.value)
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
XCTAssertEqual(Defaults[.persistentHistoryDictionary]["0"]?.value, persistentHistoryValue.value)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
Defaults[.persistentHistoryDictionary]["0"] = newPersistentHistory
|
||||
XCTAssertEqual(Defaults[.persistentHistoryDictionary]["0"]?.value, newPersistentHistory.value)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKeyCombine", default: persistentHistoryValue)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue.value, $0.newValue.value) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(persistentHistoryValue.value, newPersistentHistory.value), (newPersistentHistory.value, persistentHistoryValue.value)].enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = newPersistentHistory
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory?>("observeNSSecureCodingOptionalKeyCombine")
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue?.value, $0.newValue?.value) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(ExamplePersistentHistory?, ExamplePersistentHistory?)] = [(nil, persistentHistoryValue), (persistentHistoryValue, newPersistentHistory), (newPersistentHistory, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0?.value, tuples[index].0)
|
||||
XCTAssertEqual(expected.1?.value, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = persistentHistoryValue
|
||||
Defaults[key] = newPersistentHistory
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[ExamplePersistentHistory]>("observeNSSecureCodingArrayKeyCombine", default: [persistentHistoryValue])
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(ExamplePersistentHistory, ExamplePersistentHistory)] = [(persistentHistoryValue, newPersistentHistory), (newPersistentHistory, persistentHistoryValue)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0.value, tuples[index].0[0].value)
|
||||
XCTAssertEqual(expected.1.value, tuples[index].1[0].value)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = newPersistentHistory
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: ExamplePersistentHistory]>("observeNSSecureCodingDictionaryKeyCombine", default: ["0": persistentHistoryValue])
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(ExamplePersistentHistory, ExamplePersistentHistory)] = [(persistentHistoryValue, newPersistentHistory), (newPersistentHistory, persistentHistoryValue)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0.value, tuples[index].0["0"]?.value)
|
||||
XCTAssertEqual(expected.1.value, tuples[index].1["0"]?.value)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = newPersistentHistory
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveMultipleNSSecureKeysCombine() {
|
||||
let key1 = Defaults.Key<ExamplePersistentHistory>("observeMultipleNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let key2 = Defaults.Key<ExamplePersistentHistory>("observeMultipleNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
|
||||
|
||||
let cancellable = publisher.sink { _ in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
|
||||
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveMultipleNSSecureOptionalKeysCombine() {
|
||||
let key1 = Defaults.Key<ExamplePersistentHistory?>("observeMultipleNSSecureCodingOptionalKey1")
|
||||
let key2 = Defaults.Key<ExamplePersistentHistory?>("observeMultipleNSSecureCodingOptionalKeyKey2")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
|
||||
|
||||
let cancellable = publisher.sink { _ in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
|
||||
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
func testObserveMultipleNSSecureKeys() {
|
||||
let key1 = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let key2 = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
var counter = 0
|
||||
observation = Defaults.observe(keys: key1, key2, options: []) {
|
||||
counter += 1
|
||||
if counter == 2 {
|
||||
expect.fulfill()
|
||||
} else if counter > 2 {
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
|
||||
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testRemoveDuplicatesObserveNSSecureCodingKeyCombine() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
|
||||
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3"]
|
||||
|
||||
let cancellable = Defaults
|
||||
.publisher(key, options: [])
|
||||
.removeDuplicates()
|
||||
.map(\.newValue.value)
|
||||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
}
|
||||
|
||||
inputArray.forEach {
|
||||
Defaults[key] = ExamplePersistentHistory(value: $0)
|
||||
}
|
||||
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testRemoveDuplicatesObserveNSSecureCodingOptionalKeyCombine() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory?>("observeNSSecureCodingOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
|
||||
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3", nil]
|
||||
|
||||
let cancellable = Defaults
|
||||
.publisher(key, options: [])
|
||||
.removeDuplicates()
|
||||
.map(\.newValue)
|
||||
.map { $0?.value }
|
||||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
}
|
||||
|
||||
inputArray.forEach {
|
||||
Defaults[key] = ExamplePersistentHistory(value: $0)
|
||||
}
|
||||
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey", default: persistentHistoryValue)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue.value, persistentHistoryValue.value)
|
||||
XCTAssertEqual(change.newValue.value, newPersistentHistory.value)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = newPersistentHistory
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<ExamplePersistentHistory?>("observeNSSecureCodingOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue?.value, persistentHistoryValue.value)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = persistentHistoryValue
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[ExamplePersistentHistory]>("observeNSSecureCodingArrayKey", default: [persistentHistoryValue])
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0].value, persistentHistoryValue.value)
|
||||
XCTAssertEqual(change.newValue.map { $0.value }, [persistentHistoryValue, newPersistentHistory].map { $0.value })
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].append(newPersistentHistory)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: ExamplePersistentHistory]>("observeNSSecureCodingDictionaryKey", default: ["0": persistentHistoryValue])
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"]?.value, persistentHistoryValue.value)
|
||||
XCTAssertEqual(change.newValue["0"]?.value, persistentHistoryValue.value)
|
||||
XCTAssertEqual(change.newValue["1"]?.value, newPersistentHistory.value)
|
||||
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = newPersistentHistory
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
import Defaults
|
||||
|
||||
private struct Item: Equatable, Hashable {
|
||||
let name: String
|
||||
let count: UInt
|
||||
}
|
||||
|
||||
extension Item: Defaults.Serializable {
|
||||
static let bridge = ItemBridge()
|
||||
}
|
||||
|
||||
private struct ItemBridge: Defaults.Bridge {
|
||||
typealias Value = Item
|
||||
typealias Serializable = [String: String]
|
||||
func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ["name": value.name, "count": String(value.count)]
|
||||
}
|
||||
|
||||
func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard
|
||||
let object = object,
|
||||
let name = object["name"],
|
||||
let count = UInt(object["count"] ?? "0")
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Value(name: name, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
private let fixtureSetAlgebra = Item(name: "Apple", count: 10)
|
||||
private let fixtureSetAlgebra1 = Item(name: "Banana", count: 20)
|
||||
private let fixtureSetAlgebra2 = Item(name: "Grape", count: 30)
|
||||
private let fixtureSetAlgebra3 = Item(name: "Guava", count: 40)
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let setAlgebraCustomElement = Key<DefaultsSetAlgebra<Item>>("setAlgebraCustomElement", default: .init([fixtureSetAlgebra]))
|
||||
fileprivate static let setAlgebraCustomElementArray = Key<[DefaultsSetAlgebra<Item>]>("setAlgebraArrayCustomElement", default: [.init([fixtureSetAlgebra])])
|
||||
fileprivate static let setAlgebraCustomElementDictionary = Key<[String: DefaultsSetAlgebra<Item>]>("setAlgebraDictionaryCustomElement", default: ["0": .init([fixtureSetAlgebra])])
|
||||
}
|
||||
|
||||
final class DefaultsSetAlgebraCustomElementTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Item>>("independentSetAlgebraKey", default: .init([fixtureSetAlgebra]))
|
||||
Defaults[key].insert(fixtureSetAlgebra)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
|
||||
Defaults[key].insert(fixtureSetAlgebra1)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Item>?>("independentSetAlgebraOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = .init([fixtureSetAlgebra])
|
||||
Defaults[key]?.insert(fixtureSetAlgebra)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
|
||||
Defaults[key]?.insert(fixtureSetAlgebra1)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]>("independentSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
|
||||
Defaults[key][0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key].append(.init([fixtureSetAlgebra2]))
|
||||
Defaults[key][1].insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]?>("independentSetAlgebraArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [.init([fixtureSetAlgebra])]
|
||||
Defaults[key]?[0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key]?.append(.init([fixtureSetAlgebra2]))
|
||||
Defaults[key]?[1].insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]?[1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[DefaultsSetAlgebra<Item>]]>("independentSetAlgebraNestedArrayKey", default: [[.init([fixtureSetAlgebra])]])
|
||||
Defaults[key][0][0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key][0].append(.init([fixtureSetAlgebra1]))
|
||||
Defaults[key][0][1].insert(fixtureSetAlgebra2)
|
||||
Defaults[key].append([.init([fixtureSetAlgebra3])])
|
||||
Defaults[key][1][0].insert(fixtureSetAlgebra2)
|
||||
XCTAssertEqual(Defaults[key][0][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key][0][1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
|
||||
XCTAssertEqual(Defaults[key][1][0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: DefaultsSetAlgebra<Item>]]>("independentSetAlgebraArrayDictionaryKey", default: [["0": .init([fixtureSetAlgebra])]])
|
||||
Defaults[key][0]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[key][0]["1"] = .init([fixtureSetAlgebra1])
|
||||
Defaults[key][0]["1"]?.insert(fixtureSetAlgebra2)
|
||||
Defaults[key].append(["0": .init([fixtureSetAlgebra3])])
|
||||
Defaults[key][1]["0"]?.insert(fixtureSetAlgebra2)
|
||||
XCTAssertEqual(Defaults[key][0]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key][0]["1"], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
|
||||
XCTAssertEqual(Defaults[key][1]["0"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]>("independentSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
|
||||
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[key]["1"] = .init([fixtureSetAlgebra2])
|
||||
Defaults[key]["1"]?.insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]?>("independentSetAlgebraDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": .init([fixtureSetAlgebra])]
|
||||
Defaults[key]?["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[key]?["1"] = .init([fixtureSetAlgebra2])
|
||||
Defaults[key]?["1"]?.insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key]?["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]?["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [DefaultsSetAlgebra<Item>]]>("independentSetAlgebraDictionaryArrayKey", default: ["0": [.init([fixtureSetAlgebra])]])
|
||||
Defaults[key]["0"]?[0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key]["0"]?.append(.init([fixtureSetAlgebra1]))
|
||||
Defaults[key]["0"]?[1].insert(fixtureSetAlgebra2)
|
||||
Defaults[key]["1"] = [.init([fixtureSetAlgebra3])]
|
||||
Defaults[key]["1"]?[0].insert(fixtureSetAlgebra2)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testType() {
|
||||
let (inserted, _) = Defaults[.setAlgebraCustomElement].insert(fixtureSetAlgebra)
|
||||
XCTAssertFalse(inserted)
|
||||
Defaults[.setAlgebraCustomElement].insert(fixtureSetAlgebra1)
|
||||
XCTAssertEqual(Defaults[.setAlgebraCustomElement], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
Defaults[.setAlgebraCustomElementArray][0].insert(fixtureSetAlgebra1)
|
||||
Defaults[.setAlgebraCustomElementArray].append(.init([fixtureSetAlgebra2]))
|
||||
Defaults[.setAlgebraCustomElementArray][1].insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[.setAlgebraCustomElementArray][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[.setAlgebraCustomElementArray][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
Defaults[.setAlgebraCustomElementDictionary]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[.setAlgebraCustomElementDictionary]["1"] = .init([fixtureSetAlgebra2])
|
||||
Defaults[.setAlgebraCustomElementDictionary]["1"]?.insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[.setAlgebraCustomElementDictionary]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[.setAlgebraCustomElementDictionary]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Item>>("observeSetAlgebraKeyCombine", default: .init([fixtureSetAlgebra]))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Item>, DefaultsSetAlgebra<Item>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Item>?>("observeSetAlgebraOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Item>?, DefaultsSetAlgebra<Item>?)] = [(nil, .init([fixtureSetAlgebra])), (.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .init([fixtureSetAlgebra])
|
||||
Defaults[key]?.insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]>("observeSetAlgebraArrayKeyCombine", default: [.init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Item>, DefaultsSetAlgebra<Item>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0].insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]>("observeSetAlgebraDictionaryKeyCombine", default: ["0": .init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Item>, DefaultsSetAlgebra<Item>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Item>>("observeSetAlgebraKey", default: .init([fixtureSetAlgebra]))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, .init([fixtureSetAlgebra]))
|
||||
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(fixtureSetAlgebra1)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Item>?>("observeSetAlgebraOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .init([fixtureSetAlgebra])
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]>("observeSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0], .init([fixtureSetAlgebra]))
|
||||
XCTAssertEqual(change.newValue[1], .init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].append(.init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictioanryKey() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]>("observeSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"], .init([fixtureSetAlgebra]))
|
||||
XCTAssertEqual(change.newValue["1"], .init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = .init([fixtureSetAlgebra])
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
import Defaults
|
||||
|
||||
struct DefaultsSetAlgebra<Element: Defaults.Serializable & Hashable>: SetAlgebra {
|
||||
var store = Set<Element>()
|
||||
|
||||
init() {}
|
||||
|
||||
init<S: Sequence>(_ sequence: __owned S) where Element == S.Element {
|
||||
self.store = Set(sequence)
|
||||
}
|
||||
|
||||
init(_ store: Set<Element>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
func contains(_ member: Element) -> Bool {
|
||||
store.contains(member)
|
||||
}
|
||||
|
||||
func union(_ other: DefaultsSetAlgebra) -> DefaultsSetAlgebra {
|
||||
DefaultsSetAlgebra(store.union(other.store))
|
||||
}
|
||||
|
||||
func intersection(_ other: DefaultsSetAlgebra)
|
||||
-> DefaultsSetAlgebra {
|
||||
var defaultsSetAlgebra = DefaultsSetAlgebra()
|
||||
defaultsSetAlgebra.store = store.intersection(other.store)
|
||||
return defaultsSetAlgebra
|
||||
}
|
||||
|
||||
func symmetricDifference(_ other: DefaultsSetAlgebra)
|
||||
-> DefaultsSetAlgebra {
|
||||
var defaultedSetAlgebra = DefaultsSetAlgebra()
|
||||
defaultedSetAlgebra.store = store.symmetricDifference(other.store)
|
||||
return defaultedSetAlgebra
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
mutating func insert(_ newMember: Element)
|
||||
-> (inserted: Bool, memberAfterInsert: Element) {
|
||||
store.insert(newMember)
|
||||
}
|
||||
|
||||
mutating func remove(_ member: Element) -> Element? {
|
||||
store.remove(member)
|
||||
}
|
||||
|
||||
mutating func update(with newMember: Element) -> Element? {
|
||||
store.update(with: newMember)
|
||||
}
|
||||
|
||||
mutating func formUnion(_ other: DefaultsSetAlgebra) {
|
||||
store.formUnion(other.store)
|
||||
}
|
||||
|
||||
mutating func formSymmetricDifference(_ other: DefaultsSetAlgebra) {
|
||||
store.formSymmetricDifference(other.store)
|
||||
}
|
||||
|
||||
mutating func formIntersection(_ other: DefaultsSetAlgebra) {
|
||||
store.formIntersection(other.store)
|
||||
}
|
||||
}
|
||||
|
||||
extension DefaultsSetAlgebra: Defaults.SetAlgebraSerializable {
|
||||
func toArray() -> [Element] {
|
||||
Array(store)
|
||||
}
|
||||
}
|
||||
|
||||
private let fixtureSetAlgebra = 0
|
||||
private let fixtureSetAlgebra1 = 1
|
||||
private let fixtureSetAlgebra2 = 2
|
||||
private let fixtureSetAlgebra3 = 3
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let setAlgebra = Key<DefaultsSetAlgebra<Int>>("setAlgebra", default: .init([fixtureSetAlgebra]))
|
||||
fileprivate static let setAlgebraArray = Key<[DefaultsSetAlgebra<Int>]>("setAlgebraArray", default: [.init([fixtureSetAlgebra])])
|
||||
fileprivate static let setAlgebraDictionary = Key<[String: DefaultsSetAlgebra<Int>]>("setAlgebraDictionary", default: ["0": .init([fixtureSetAlgebra])])
|
||||
}
|
||||
|
||||
final class DefaultsSetAlgebraTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Int>>("independentSetAlgebraKey", default: .init([fixtureSetAlgebra]))
|
||||
Defaults[key].insert(fixtureSetAlgebra)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
|
||||
Defaults[key].insert(fixtureSetAlgebra1)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Int>?>("independentSetAlgebraOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = .init([fixtureSetAlgebra])
|
||||
Defaults[key]?.insert(fixtureSetAlgebra)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
|
||||
Defaults[key]?.insert(fixtureSetAlgebra1)
|
||||
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]>("independentSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
|
||||
Defaults[key][0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key].append(.init([fixtureSetAlgebra2]))
|
||||
Defaults[key][1].insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]?>("independentSetAlgebraArrayOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [.init([fixtureSetAlgebra])]
|
||||
Defaults[key]?[0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key]?.append(.init([fixtureSetAlgebra2]))
|
||||
Defaults[key]?[1].insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]?[1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[DefaultsSetAlgebra<Int>]]>("independentSetAlgebraNestedArrayKey", default: [[.init([fixtureSetAlgebra])]])
|
||||
Defaults[key][0][0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key][0].append(.init([fixtureSetAlgebra1]))
|
||||
Defaults[key][0][1].insert(fixtureSetAlgebra2)
|
||||
Defaults[key].append([.init([fixtureSetAlgebra3])])
|
||||
Defaults[key][1][0].insert(fixtureSetAlgebra2)
|
||||
XCTAssertEqual(Defaults[key][0][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key][0][1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
|
||||
XCTAssertEqual(Defaults[key][1][0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: DefaultsSetAlgebra<Int>]]>("independentSetAlgebraArrayDictionaryKey", default: [["0": .init([fixtureSetAlgebra])]])
|
||||
Defaults[key][0]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[key][0]["1"] = .init([fixtureSetAlgebra1])
|
||||
Defaults[key][0]["1"]?.insert(fixtureSetAlgebra2)
|
||||
Defaults[key].append(["0": .init([fixtureSetAlgebra3])])
|
||||
Defaults[key][1]["0"]?.insert(fixtureSetAlgebra2)
|
||||
XCTAssertEqual(Defaults[key][0]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key][0]["1"], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
|
||||
XCTAssertEqual(Defaults[key][1]["0"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]>("independentSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
|
||||
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[key]["1"] = .init([fixtureSetAlgebra2])
|
||||
Defaults[key]["1"]?.insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]?>("independentSetAlgebraDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": .init([fixtureSetAlgebra])]
|
||||
Defaults[key]?["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[key]?["1"] = .init([fixtureSetAlgebra2])
|
||||
Defaults[key]?["1"]?.insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[key]?["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]?["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [DefaultsSetAlgebra<Int>]]>("independentSetAlgebraDictionaryArrayKey", default: ["0": [.init([fixtureSetAlgebra])]])
|
||||
Defaults[key]["0"]?[0].insert(fixtureSetAlgebra1)
|
||||
Defaults[key]["0"]?.append(.init([fixtureSetAlgebra1]))
|
||||
Defaults[key]["0"]?[1].insert(fixtureSetAlgebra2)
|
||||
Defaults[key]["1"] = [.init([fixtureSetAlgebra3])]
|
||||
Defaults[key]["1"]?[0].insert(fixtureSetAlgebra2)
|
||||
XCTAssertEqual(Defaults[key]["0"]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[key]["0"]?[1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
|
||||
XCTAssertEqual(Defaults[key]["1"]?[0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testType() {
|
||||
let (inserted, _) = Defaults[.setAlgebra].insert(fixtureSetAlgebra)
|
||||
XCTAssertFalse(inserted)
|
||||
Defaults[.setAlgebra].insert(fixtureSetAlgebra1)
|
||||
XCTAssertEqual(Defaults[.setAlgebra], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
Defaults[.setAlgebraArray][0].insert(fixtureSetAlgebra1)
|
||||
Defaults[.setAlgebraArray].append(.init([fixtureSetAlgebra2]))
|
||||
Defaults[.setAlgebraArray][1].insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[.setAlgebraArray][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[.setAlgebraArray][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
Defaults[.setAlgebraDictionary]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults[.setAlgebraDictionary]["1"] = .init([fixtureSetAlgebra2])
|
||||
Defaults[.setAlgebraDictionary]["1"]?.insert(fixtureSetAlgebra3)
|
||||
XCTAssertEqual(Defaults[.setAlgebraDictionary]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
XCTAssertEqual(Defaults[.setAlgebraDictionary]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Int>>("observeSetAlgebraKeyCombine", default: .init([fixtureSetAlgebra]))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Int>, DefaultsSetAlgebra<Int>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Int>?>("observeSetAlgebraOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Int>?, DefaultsSetAlgebra<Int>?)] = [(nil, .init([fixtureSetAlgebra])), (.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0)
|
||||
XCTAssertEqual(expected.1, tuples[index].1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .init([fixtureSetAlgebra])
|
||||
Defaults[key]?.insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]>("observeSetAlgebraArrayKeyCombine", default: [.init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Int>, DefaultsSetAlgebra<Int>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0[0])
|
||||
XCTAssertEqual(expected.1, tuples[index].1[0])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0].insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]>("observeSetAlgebraDictionaryKeyCombine", default: ["0": .init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let expectedValue: [(DefaultsSetAlgebra<Int>, DefaultsSetAlgebra<Int>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
XCTAssertEqual(expected.0, tuples[index].0["0"])
|
||||
XCTAssertEqual(expected.1, tuples[index].1["0"])
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Int>>("observeSetAlgebraKey", default: .init([fixtureSetAlgebra]))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, .init([fixtureSetAlgebra]))
|
||||
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].insert(fixtureSetAlgebra1)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<DefaultsSetAlgebra<Int>?>("observeSetAlgebraOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .init([fixtureSetAlgebra])
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]>("observeSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue[0], .init([fixtureSetAlgebra]))
|
||||
XCTAssertEqual(change.newValue[1], .init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].append(.init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictioanryKey() {
|
||||
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]>("observeSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue["0"], .init([fixtureSetAlgebra]))
|
||||
XCTAssertEqual(change.newValue["1"], .init([fixtureSetAlgebra]))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = .init([fixtureSetAlgebra])
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
|
||||
private let fixtureSet = Set(1...5)
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let set = Key<Set<Int>>("setInt", default: fixtureSet)
|
||||
}
|
||||
|
||||
final class DefaultsSetTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<Set<Int>>("independentSetKey", default: fixtureSet)
|
||||
XCTAssertEqual(Defaults[key].count, fixtureSet.count)
|
||||
Defaults[key].insert(6)
|
||||
XCTAssertEqual(Defaults[key], Set(1...6))
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<Set<Int>?>("independentSetOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = fixtureSet
|
||||
XCTAssertEqual(Defaults[key]?.count, fixtureSet.count)
|
||||
Defaults[key]?.insert(6)
|
||||
XCTAssertEqual(Defaults[key], Set(1...6))
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[Set<Int>]>("independentSetArrayKey", default: [fixtureSet])
|
||||
XCTAssertEqual(Defaults[key][0].count, fixtureSet.count)
|
||||
Defaults[key][0].insert(6)
|
||||
XCTAssertEqual(Defaults[key][0], Set(1...6))
|
||||
Defaults[key].append(Set(1...4))
|
||||
XCTAssertEqual(Defaults[key][1], Set(1...4))
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: Set<Int>]>("independentSetArrayKey", default: ["0": fixtureSet])
|
||||
XCTAssertEqual(Defaults[key]["0"]?.count, fixtureSet.count)
|
||||
Defaults[key]["0"]?.insert(6)
|
||||
XCTAssertEqual(Defaults[key]["0"], Set(1...6))
|
||||
Defaults[key]["1"] = Set(1...4)
|
||||
XCTAssertEqual(Defaults[key]["1"], Set(1...4))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import XCTest
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let hasUnicorn = Key<Bool>("swiftui_hasUnicorn", default: false)
|
||||
fileprivate static let user = Key<User>("swiftui_user", default: User(username: "Hank", password: "123456"))
|
||||
fileprivate static let setInt = Key<Set<Int>>("swiftui_setInt", default: Set(1...3))
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
struct ContentView: View {
|
||||
@Default(.hasUnicorn) var hasUnicorn
|
||||
@Default(.user) var user
|
||||
@Default(.setInt) var setInt
|
||||
|
||||
var body: some View {
|
||||
Text("User \(user.username) has Unicorn: \(String(hasUnicorn))")
|
||||
Toggle("Toggle Unicorn", isOn: $hasUnicorn)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
final class DefaultsSwiftUITests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testSwiftUIObserve() {
|
||||
let view = ContentView()
|
||||
XCTAssertFalse(view.hasUnicorn)
|
||||
XCTAssertEqual(view.user.username, "Hank")
|
||||
XCTAssertEqual(view.setInt.count, 3)
|
||||
view.user = User(username: "Chen", password: "123456")
|
||||
view.hasUnicorn.toggle()
|
||||
view.setInt.insert(4)
|
||||
XCTAssertTrue(view.hasUnicorn)
|
||||
XCTAssertEqual(view.user.username, "Chen")
|
||||
XCTAssertEqual(view.setInt, Set(1...4))
|
||||
}
|
||||
}
|
|
@ -1,53 +1,20 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import Combine
|
||||
import XCTest
|
||||
import Defaults
|
||||
|
||||
let fixtureURL = URL(string: "https://sindresorhus.com")!
|
||||
let fixtureFileURL = URL(string: "file://~/icon.png")!
|
||||
let fixtureURL2 = URL(string: "https://example.com")!
|
||||
|
||||
enum FixtureEnum: String, Codable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
|
||||
let fixtureDate = Date()
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
final class ExamplePersistentHistory: NSPersistentHistoryToken {
|
||||
let value: String
|
||||
|
||||
init(value: String) {
|
||||
self.value = value
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
self.value = coder.decodeObject(forKey: "value") as! String
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func encode(with coder: NSCoder) {
|
||||
coder.encode(value, forKey: "value")
|
||||
}
|
||||
|
||||
override class var supportsSecureCoding: Bool { true }
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let key = Key<Bool>("key", default: false)
|
||||
static let url = Key<URL>("url", default: fixtureURL)
|
||||
static let `enum` = Key<FixtureEnum>("enum", default: .oneHour)
|
||||
static let file = Key<URL>("fileURL", default: fixtureFileURL)
|
||||
static let data = Key<Data>("data", default: Data([]))
|
||||
static let date = Key<Date>("date", default: fixtureDate)
|
||||
|
||||
// NSSecureCoding
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
static let persistentHistoryValue = ExamplePersistentHistory(value: "ExampleToken")
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
static let persistentHistory = NSSecureCodingKey<ExamplePersistentHistory>("persistentHistory", default: persistentHistoryValue)
|
||||
}
|
||||
|
||||
final class DefaultsTests: XCTestCase {
|
||||
|
@ -70,13 +37,21 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<Bool?>("independentOptionalKey")
|
||||
let url = Defaults.Key<URL?>("independentOptionalURLKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
XCTAssertNil(Defaults[url])
|
||||
Defaults[key] = true
|
||||
Defaults[url] = fixtureURL
|
||||
XCTAssertTrue(Defaults[key]!)
|
||||
XCTAssertEqual(Defaults[url], fixtureURL)
|
||||
Defaults[key] = nil
|
||||
Defaults[url] = nil
|
||||
XCTAssertNil(Defaults[key])
|
||||
XCTAssertNil(Defaults[url])
|
||||
Defaults[key] = false
|
||||
Defaults[url] = fixtureURL2
|
||||
XCTAssertFalse(Defaults[key]!)
|
||||
XCTAssertEqual(Defaults[url], fixtureURL2)
|
||||
}
|
||||
|
||||
func testKeyRegistersDefault() {
|
||||
|
@ -104,29 +79,15 @@ final class DefaultsTests: XCTestCase {
|
|||
XCTAssertTrue(Defaults[.key])
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
func testNSSecureCodingKeys() {
|
||||
XCTAssertEqual(Defaults.Keys.persistentHistoryValue.value, Defaults[.persistentHistory].value)
|
||||
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
|
||||
Defaults[.persistentHistory] = newPersistentHistory
|
||||
XCTAssertEqual(newPersistentHistory.value, Defaults[.persistentHistory].value)
|
||||
}
|
||||
|
||||
func testUrlType() {
|
||||
XCTAssertEqual(Defaults[.url], fixtureURL)
|
||||
|
||||
let newUrl = URL(string: "https://twitter.com")!
|
||||
Defaults[.url] = newUrl
|
||||
XCTAssertEqual(Defaults[.url], newUrl)
|
||||
}
|
||||
|
||||
func testEnumType() {
|
||||
XCTAssertEqual(Defaults[.enum], FixtureEnum.oneHour)
|
||||
}
|
||||
|
||||
func testDataType() {
|
||||
XCTAssertEqual(Defaults[.data], Data([]))
|
||||
|
||||
let newData = Data([0xFF])
|
||||
Defaults[.data] = newData
|
||||
XCTAssertEqual(Defaults[.data], newData)
|
||||
|
@ -134,12 +95,15 @@ final class DefaultsTests: XCTestCase {
|
|||
|
||||
func testDateType() {
|
||||
XCTAssertEqual(Defaults[.date], fixtureDate)
|
||||
|
||||
let newDate = Date()
|
||||
Defaults[.date] = newDate
|
||||
XCTAssertEqual(Defaults[.date], newDate)
|
||||
}
|
||||
|
||||
func testFileURLType() {
|
||||
XCTAssertEqual(Defaults[.file], fixtureFileURL)
|
||||
}
|
||||
|
||||
func testRemoveAll() {
|
||||
let key = Defaults.Key<Bool>("removeAll", default: false)
|
||||
let key2 = Defaults.Key<Bool>("removeAll2", default: false)
|
||||
|
@ -189,39 +153,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveNSSecureCodingKeyCombine() {
|
||||
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue.value, $0.newValue.value) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValues = [
|
||||
("TestValue", "NewTestValue"),
|
||||
("NewTestValue", "NewTestValue2"),
|
||||
("NewTestValue2", "TestValue")
|
||||
]
|
||||
|
||||
let cancellable = publisher.sink { actualValues in
|
||||
for (expected, actual) in zip(expectedValues, actualValues) {
|
||||
XCTAssertEqual(expected.0, actual.0)
|
||||
XCTAssertEqual(expected.1, actual.1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue")
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<Bool?>("observeOptionalKey")
|
||||
|
@ -251,40 +182,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveNSSecureCodingOptionalKeyCombine() {
|
||||
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue?.value, $0.newValue?.value) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValues: [(String?, String?)] = [
|
||||
(nil, "NewTestValue"),
|
||||
("NewTestValue", "NewTestValue2"),
|
||||
("NewTestValue2", nil)
|
||||
]
|
||||
|
||||
let cancellable = publisher.sink { actualValues in
|
||||
for (expected, actual) in zip(expectedValues, actualValues) {
|
||||
XCTAssertEqual(expected.0, actual.0)
|
||||
XCTAssertEqual(expected.1, actual.1)
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue")
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveMultipleKeysCombine() {
|
||||
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
||||
|
@ -304,26 +201,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveMultipleNSSecureKeysCombine() {
|
||||
let key1 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let key2 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
|
||||
|
||||
let cancellable = publisher.sink { _ in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
|
||||
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveMultipleOptionalKeysCombine() {
|
||||
let key1 = Defaults.Key<String?>("observeOptionalKey1")
|
||||
|
@ -343,25 +220,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveMultipleNSSecureOptionalKeysCombine() {
|
||||
let key1 = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingKey1")
|
||||
let key2 = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingKey2")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
|
||||
|
||||
let cancellable = publisher.sink { _ in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
|
||||
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testReceiveValueBeforeSubscriptionCombine() {
|
||||
let key = Defaults.Key<String>("receiveValueBeforeSubscription", default: "hello")
|
||||
|
@ -400,24 +258,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
func testObserveNSSecureCodingKey() {
|
||||
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue.value, "TestValue")
|
||||
XCTAssertEqual(change.newValue.value, "NewTestValue")
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue")
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<Bool?>("observeOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
@ -435,24 +275,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
func testObserveNSSecureCodingOptionalKey() {
|
||||
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertEqual(change.newValue?.value, "NewOptionalValue")
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = ExamplePersistentHistory(value: "NewOptionalValue")
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveMultipleKeys() {
|
||||
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
||||
let key2 = Defaults.Key<Bool>("observeKey2", default: true)
|
||||
|
@ -476,33 +298,7 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
|
||||
func testObserveMultipleNSSecureKeys() {
|
||||
let key1 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let key2 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
var counter = 0
|
||||
observation = Defaults.observe(keys: key1, key2, options: []) {
|
||||
counter += 1
|
||||
if counter == 2 {
|
||||
expect.fulfill()
|
||||
} else if counter > 2 {
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
|
||||
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKeyURL() {
|
||||
let fixtureURL = URL(string: "https://sindresorhus.com")!
|
||||
let fixtureURL2 = URL(string: "https://example.com")!
|
||||
let key = Defaults.Key<URL>("observeKeyURL", default: fixtureURL)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
|
@ -519,23 +315,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKeyEnum() {
|
||||
let key = Defaults.Key<FixtureEnum>("observeKeyEnum", default: .oneHour)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertEqual(change.oldValue, .oneHour)
|
||||
XCTAssertEqual(change.newValue, .tenMinutes)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = .tenMinutes
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObservePreventPropagation() {
|
||||
let key1 = Defaults.Key<Bool?>("preventPropagation0", default: nil)
|
||||
let expect = expectation(description: "No infinite recursion")
|
||||
|
@ -594,7 +373,7 @@ final class DefaultsTests: XCTestCase {
|
|||
XCTAssert(Defaults[key1]! == 4)
|
||||
expect.fulfill()
|
||||
} else {
|
||||
usleep(100_000)
|
||||
usleep(300_000)
|
||||
print("--- Release: \(Thread.isMainThread)")
|
||||
}
|
||||
}
|
||||
|
@ -699,120 +478,6 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testResetKey() {
|
||||
let defaultFixture1 = "foo1"
|
||||
let defaultFixture2 = 0
|
||||
let defaultFixture3 = "foo3"
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String>("key1", default: defaultFixture1)
|
||||
let key2 = Defaults.Key<Int>("key2", default: defaultFixture2)
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults.reset(key1)
|
||||
XCTAssertEqual(Defaults[key1], defaultFixture1)
|
||||
XCTAssertEqual(Defaults[key2], newFixture2)
|
||||
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
|
||||
let key3 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("key3", default: ExamplePersistentHistory(value: defaultFixture3))
|
||||
Defaults[key3] = ExamplePersistentHistory(value: newFixture3)
|
||||
Defaults.reset(key3)
|
||||
|
||||
XCTAssertEqual(Defaults[key3].value, defaultFixture3)
|
||||
}
|
||||
}
|
||||
|
||||
func testResetMultipleKeys() {
|
||||
let defaultFxiture1 = "foo1"
|
||||
let defaultFixture2 = 0
|
||||
let defaultFixture3 = "foo3"
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String>("akey1", default: defaultFxiture1)
|
||||
let key2 = Defaults.Key<Int>("akey2", default: defaultFixture2)
|
||||
let key3 = Defaults.Key<String>("akey3", default: defaultFixture3)
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults[key3] = newFixture3
|
||||
Defaults.reset(key1, key2)
|
||||
XCTAssertEqual(Defaults[key1], defaultFxiture1)
|
||||
XCTAssertEqual(Defaults[key2], defaultFixture2)
|
||||
XCTAssertEqual(Defaults[key3], newFixture3)
|
||||
}
|
||||
|
||||
func testResetOptionalKey() {
|
||||
let newString1 = "bar1"
|
||||
let newString2 = "bar2"
|
||||
let newString3 = "bar3"
|
||||
let key1 = Defaults.Key<String?>("optionalKey1")
|
||||
let key2 = Defaults.Key<String?>("optionalKey2")
|
||||
Defaults[key1] = newString1
|
||||
Defaults[key2] = newString2
|
||||
Defaults.reset(key1)
|
||||
XCTAssertNil(Defaults[key1])
|
||||
XCTAssertEqual(Defaults[key2], newString2)
|
||||
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
|
||||
let key3 = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("optionalKey3")
|
||||
Defaults[key3] = ExamplePersistentHistory(value: newString3)
|
||||
Defaults.reset(key3)
|
||||
XCTAssertNil(Defaults[key3])
|
||||
}
|
||||
}
|
||||
|
||||
func testResetMultipleOptionalKeys() {
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String?>("aoptionalKey1")
|
||||
let key2 = Defaults.Key<Int?>("aoptionalKey2")
|
||||
let key3 = Defaults.Key<String?>("aoptionalKey3")
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults[key3] = newFixture3
|
||||
Defaults.reset(key1, key2)
|
||||
XCTAssertNil(Defaults[key1])
|
||||
XCTAssertNil(Defaults[key2])
|
||||
XCTAssertEqual(Defaults[key3], newFixture3)
|
||||
}
|
||||
|
||||
func testObserveWithLifetimeTie() {
|
||||
let key = Defaults.Key<Bool>("lifetimeTie", default: false)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
weak var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { _ in
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
.tieToLifetime(of: self)
|
||||
|
||||
Defaults[key] = true
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveWithLifetimeTieManualBreak() {
|
||||
let key = Defaults.Key<Bool>("lifetimeTieManualBreak", default: false)
|
||||
|
||||
weak var observation: Defaults.Observation? = Defaults.observe(key, options: []) { _ in }.tieToLifetime(of: self)
|
||||
observation!.removeLifetimeTie()
|
||||
|
||||
for index in 1...10 {
|
||||
if observation == nil {
|
||||
break
|
||||
}
|
||||
|
||||
sleep(1)
|
||||
|
||||
if index == 10 {
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testRemoveDuplicatesObserveKeyCombine() {
|
||||
let key = Defaults.Key<Bool>("observeKey", default: false)
|
||||
|
@ -869,61 +534,88 @@ final class DefaultsTests: XCTestCase {
|
|||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testRemoveDuplicatesObserveNSSecureCodingKeyCombine() {
|
||||
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
|
||||
func testResetKey() {
|
||||
let defaultFixture1 = "foo1"
|
||||
let defaultFixture2 = 0
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let key1 = Defaults.Key<String>("key1", default: defaultFixture1)
|
||||
let key2 = Defaults.Key<Int>("key2", default: defaultFixture2)
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults.reset(key1)
|
||||
XCTAssertEqual(Defaults[key1], defaultFixture1)
|
||||
XCTAssertEqual(Defaults[key2], newFixture2)
|
||||
}
|
||||
|
||||
func testResetMultipleKeys() {
|
||||
let defaultFxiture1 = "foo1"
|
||||
let defaultFixture2 = 0
|
||||
let defaultFixture3 = "foo3"
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String>("akey1", default: defaultFxiture1)
|
||||
let key2 = Defaults.Key<Int>("akey2", default: defaultFixture2)
|
||||
let key3 = Defaults.Key<String>("akey3", default: defaultFixture3)
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults[key3] = newFixture3
|
||||
Defaults.reset(key1, key2)
|
||||
XCTAssertEqual(Defaults[key1], defaultFxiture1)
|
||||
XCTAssertEqual(Defaults[key2], defaultFixture2)
|
||||
XCTAssertEqual(Defaults[key3], newFixture3)
|
||||
}
|
||||
|
||||
func testResetMultipleOptionalKeys() {
|
||||
let newFixture1 = "bar1"
|
||||
let newFixture2 = 1
|
||||
let newFixture3 = "bar3"
|
||||
let key1 = Defaults.Key<String?>("aoptionalKey1")
|
||||
let key2 = Defaults.Key<Int?>("aoptionalKey2")
|
||||
let key3 = Defaults.Key<String?>("aoptionalKey3")
|
||||
Defaults[key1] = newFixture1
|
||||
Defaults[key2] = newFixture2
|
||||
Defaults[key3] = newFixture3
|
||||
Defaults.reset(key1, key2)
|
||||
XCTAssertNil(Defaults[key1])
|
||||
XCTAssertNil(Defaults[key2])
|
||||
XCTAssertEqual(Defaults[key3], newFixture3)
|
||||
}
|
||||
|
||||
func testObserveWithLifetimeTie() {
|
||||
let key = Defaults.Key<Bool>("lifetimeTie", default: false)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
|
||||
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3"]
|
||||
|
||||
let cancellable = Defaults
|
||||
.publisher(key, options: [])
|
||||
.removeDuplicates()
|
||||
.map(\.newValue.value)
|
||||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
weak var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { _ in
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
.tieToLifetime(of: self)
|
||||
|
||||
inputArray.forEach {
|
||||
Defaults[key] = ExamplePersistentHistory(value: $0)
|
||||
}
|
||||
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
Defaults[key] = true
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testRemoveDuplicatesObserveNSSecureCodingOptionalKeyCombine() {
|
||||
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
func testObserveWithLifetimeTieManualBreak() {
|
||||
let key = Defaults.Key<Bool>("lifetimeTieManualBreak", default: false)
|
||||
|
||||
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
|
||||
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3", nil]
|
||||
weak var observation: Defaults.Observation? = Defaults.observe(key, options: []) { _ in }.tieToLifetime(of: self)
|
||||
observation!.removeLifetimeTie()
|
||||
|
||||
let cancellable = Defaults
|
||||
.publisher(key, options: [])
|
||||
.removeDuplicates()
|
||||
.map(\.newValue)
|
||||
.map { $0?.value }
|
||||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
for index in 1...10 {
|
||||
if observation == nil {
|
||||
break
|
||||
}
|
||||
|
||||
inputArray.forEach {
|
||||
Defaults[key] = ExamplePersistentHistory(value: $0)
|
||||
sleep(1)
|
||||
|
||||
if index == 10 {
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
#if !os(macOS)
|
||||
import Foundation
|
||||
import Defaults
|
||||
import XCTest
|
||||
import UIKit
|
||||
|
||||
private let fixtureColor = UIColor(red: CGFloat(103) / CGFloat(0xFF), green: CGFloat(132) / CGFloat(0xFF), blue: CGFloat(255) / CGFloat(0xFF), alpha: 1)
|
||||
private let fixtureColor1 = UIColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(241) / CGFloat(0xFF), blue: CGFloat(180) / CGFloat(0xFF), alpha: 1)
|
||||
private let fixtureColor2 = UIColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(180) / CGFloat(0xFF), blue: CGFloat(194) / CGFloat(0xFF), alpha: 1)
|
||||
|
||||
extension Defaults.Keys {
|
||||
fileprivate static let color = Defaults.Key<UIColor>("NSColor", default: fixtureColor)
|
||||
fileprivate static let colorArray = Defaults.Key<[UIColor]>("NSColorArray", default: [fixtureColor])
|
||||
fileprivate static let colorDictionary = Defaults.Key<[String: UIColor]>("NSColorArray", default: ["0": fixtureColor])
|
||||
}
|
||||
|
||||
final class DefaultsNSColorTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
Defaults.removeAll()
|
||||
}
|
||||
|
||||
func testKey() {
|
||||
let key = Defaults.Key<UIColor>("independentNSColorKey", default: fixtureColor)
|
||||
XCTAssertTrue(Defaults[key].isEqual(fixtureColor))
|
||||
Defaults[key] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[key].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testOptionalKey() {
|
||||
let key = Defaults.Key<UIColor?>("independentNSColorOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = fixtureColor
|
||||
XCTAssertTrue(Defaults[key]?.isEqual(fixtureColor) ?? false)
|
||||
}
|
||||
|
||||
func testArrayKey() {
|
||||
let key = Defaults.Key<[UIColor]>("independentNSColorArrayKey", default: [fixtureColor])
|
||||
XCTAssertTrue(Defaults[key][0].isEqual(fixtureColor))
|
||||
Defaults[key].append(fixtureColor1)
|
||||
XCTAssertTrue(Defaults[key][1].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testArrayOptionalKey() {
|
||||
let key = Defaults.Key<[UIColor]?>("independentNSColorOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = [fixtureColor]
|
||||
Defaults[key]?.append(fixtureColor1)
|
||||
XCTAssertTrue(Defaults[key]?[0].isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(Defaults[key]?[1].isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
func testNestedArrayKey() {
|
||||
let key = Defaults.Key<[[UIColor]]>("independentNSColorNestedArrayKey", default: [[fixtureColor]])
|
||||
XCTAssertTrue(Defaults[key][0][0].isEqual(fixtureColor))
|
||||
Defaults[key][0].append(fixtureColor1)
|
||||
Defaults[key].append([fixtureColor2])
|
||||
XCTAssertTrue(Defaults[key][0][1].isEqual(fixtureColor1))
|
||||
XCTAssertTrue(Defaults[key][1][0].isEqual(fixtureColor2))
|
||||
}
|
||||
|
||||
func testArrayDictionaryKey() {
|
||||
let key = Defaults.Key<[[String: UIColor]]>("independentNSColorArrayDictionaryKey", default: [["0": fixtureColor]])
|
||||
XCTAssertTrue(Defaults[key][0]["0"]?.isEqual(fixtureColor) ?? false)
|
||||
Defaults[key][0]["1"] = fixtureColor1
|
||||
Defaults[key].append(["0": fixtureColor2])
|
||||
XCTAssertTrue(Defaults[key][0]["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
XCTAssertTrue(Defaults[key][1]["0"]?.isEqual(fixtureColor2) ?? false)
|
||||
}
|
||||
|
||||
func testDictionaryKey() {
|
||||
let key = Defaults.Key<[String: UIColor]>("independentNSColorDictionaryKey", default: ["0": fixtureColor])
|
||||
XCTAssertTrue(Defaults[key]["0"]?.isEqual(fixtureColor) ?? false)
|
||||
Defaults[key]["1"] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[key]["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
func testDictionaryOptionalKey() {
|
||||
let key = Defaults.Key<[String: UIColor]?>("independentNSColorDictionaryOptionalKey")
|
||||
XCTAssertNil(Defaults[key])
|
||||
Defaults[key] = ["0": fixtureColor]
|
||||
Defaults[key]?["1"] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[key]?["0"]?.isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(Defaults[key]?["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
func testDictionaryArrayKey() {
|
||||
let key = Defaults.Key<[String: [UIColor]]>("independentNSColorDictionaryArrayKey", default: ["0": [fixtureColor]])
|
||||
XCTAssertTrue(Defaults[key]["0"]?[0].isEqual(fixtureColor) ?? false)
|
||||
Defaults[key]["0"]?.append(fixtureColor1)
|
||||
Defaults[key]["1"] = [fixtureColor2]
|
||||
XCTAssertTrue(Defaults[key]["0"]?[1].isEqual(fixtureColor1) ?? false)
|
||||
XCTAssertTrue(Defaults[key]["1"]?[0].isEqual(fixtureColor2) ?? false)
|
||||
}
|
||||
|
||||
func testType() {
|
||||
XCTAssert(Defaults[.color].isEqual(fixtureColor))
|
||||
Defaults[.color] = fixtureColor1
|
||||
XCTAssert(Defaults[.color].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testArrayType() {
|
||||
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor))
|
||||
Defaults[.colorArray][0] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor1))
|
||||
}
|
||||
|
||||
func testDictionaryType() {
|
||||
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor) ?? false)
|
||||
Defaults[.colorDictionary]["0"] = fixtureColor1
|
||||
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor1) ?? false)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveKeyCombine() {
|
||||
let key = Defaults.Key<UIColor>("observeNSColorKeyCombine", default: fixtureColor)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
|
||||
XCTAssertTrue(expected.0.isEqual(tuples[index].0))
|
||||
XCTAssertTrue(expected.1.isEqual(tuples[index].1))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveOptionalKeyCombine() {
|
||||
let key = Defaults.Key<UIColor?>("observeNSColorOptionalKeyCombine")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(3)
|
||||
|
||||
let expectedValue: [(UIColor?, UIColor?)] = [(nil, fixtureColor), (fixtureColor, fixtureColor1), (fixtureColor1, nil)]
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in expectedValue.enumerated() {
|
||||
guard let oldValue = expected.0 else {
|
||||
XCTAssertNil(tuples[index].0)
|
||||
continue
|
||||
}
|
||||
guard let newValue = expected.1 else {
|
||||
XCTAssertNil(tuples[index].1)
|
||||
continue
|
||||
}
|
||||
XCTAssertTrue(oldValue.isEqual(tuples[index].0))
|
||||
XCTAssertTrue(newValue.isEqual(tuples[index].1))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor
|
||||
Defaults[key] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveArrayKeyCombine() {
|
||||
let key = Defaults.Key<[UIColor]>("observeNSColorArrayKeyCombine", default: [fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
|
||||
XCTAssertTrue(expected.0.isEqual(tuples[index].0[0]))
|
||||
XCTAssertTrue(expected.1.isEqual(tuples[index].1[0]))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key][0] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
|
||||
func testObserveDictionaryKeyCombine() {
|
||||
let key = Defaults.Key<[String: UIColor]>("observeNSColorDictionaryKeyCombine", default: ["0": fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
let publisher = Defaults
|
||||
.publisher(key, options: [])
|
||||
.map { ($0.oldValue, $0.newValue) }
|
||||
.collect(2)
|
||||
|
||||
let cancellable = publisher.sink { tuples in
|
||||
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
|
||||
XCTAssertTrue(expected.0.isEqual(tuples[index].0["0"]))
|
||||
XCTAssertTrue(expected.1.isEqual(tuples[index].1["0"]))
|
||||
}
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["0"] = fixtureColor1
|
||||
Defaults.reset(key)
|
||||
cancellable.cancel()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveKey() {
|
||||
let key = Defaults.Key<UIColor>("observeNSColorKey", default: fixtureColor)
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue.isEqual(fixtureColor))
|
||||
XCTAssertTrue(change.newValue.isEqual(fixtureColor1))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor1
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveOptionalKey() {
|
||||
let key = Defaults.Key<UIColor?>("observeNSColorOptionalKey")
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertNil(change.oldValue)
|
||||
XCTAssertTrue(change.newValue?.isEqual(fixtureColor) ?? false)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key] = fixtureColor
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveArrayKey() {
|
||||
let key = Defaults.Key<[UIColor]>("observeNSColorArrayKey", default: [fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue[0].isEqual(fixtureColor))
|
||||
XCTAssertTrue(change.newValue[0].isEqual(fixtureColor))
|
||||
XCTAssertTrue(change.newValue[1].isEqual(fixtureColor1))
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key].append(fixtureColor1)
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
func testObserveDictionaryKey() {
|
||||
let key = Defaults.Key<[String: UIColor]>("observeNSColorDictionaryKey", default: ["0": fixtureColor])
|
||||
let expect = expectation(description: "Observation closure being called")
|
||||
|
||||
var observation: Defaults.Observation!
|
||||
observation = Defaults.observe(key, options: []) { change in
|
||||
XCTAssertTrue(change.oldValue["0"]?.isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(change.newValue["0"]?.isEqual(fixtureColor) ?? false)
|
||||
XCTAssertTrue(change.newValue["1"]?.isEqual(fixtureColor1) ?? false)
|
||||
observation.invalidate()
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
Defaults[key]["1"] = fixtureColor1
|
||||
observation.invalidate()
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,404 @@
|
|||
# Migration Guide From v4 to v5
|
||||
|
||||
## Warning
|
||||
|
||||
If the migration is not success or incomplete. Edit `Defaults.Key` might cause data loss.
|
||||
**Please back up your UserDefaults data before migration.**
|
||||
|
||||
## Summary
|
||||
|
||||
Before v4, `Defaults` store `Codable` types as a JSON string.
|
||||
After v5, `Defaults` store `Defaults.Serializable` types with `UserDefaults` native supported type.
|
||||
|
||||
```swift
|
||||
// Before
|
||||
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
|
||||
|
||||
UserDefaults.standard.string(forKey: "key") //=> "["0": 0]"
|
||||
|
||||
// After v5
|
||||
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
|
||||
|
||||
UserDefaults.standard.dictionary(forKey: "key") //=> [0: 0]
|
||||
```
|
||||
|
||||
All types should conform to `Defaults.Serializable` in order to work with `Defaults`.
|
||||
So this will require some migrations to resolve **TWO** major issues.
|
||||
|
||||
## Issues
|
||||
|
||||
1. **Compiler complain that `Defaults.Key<Value>` is not conform to `Defaults.Serializable`.**
|
||||
Since we replace `Codable` with `Defaults.Serializable`, `Key<Value>` will have to conform to `Value: Defaults.Serializable`.
|
||||
For this situation, please follow the guide below:
|
||||
- [From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5](#from-codable-struct-in-defaults-v4-to-codable-struct-in-defaults-v5)
|
||||
- [From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5](#from-codable-enum-in-defaults-v4-to-codable-enum-in-defaults-v5)
|
||||
|
||||
2. **Previous value in UserDefaults is not readable. (ex. `Defaults[.array]` return `null`).**
|
||||
In v5, `Defaults` reads value from `UserDefaults` as a native supported type.
|
||||
But `UserDefaults` only contains JSON string before migration, `Defaults` will not be able to work with it.
|
||||
For this situation, `Defaults` provides `Defaults.migrate` method to automate the migration process.
|
||||
- [From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Native Supported Elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-native-supported-elements-in-defaults-v5)
|
||||
- [From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Codable Elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-codable-elements-in-defaults-v5)
|
||||
|
||||
**Caution:**
|
||||
- This is a breaking change, there is no way to convert it back to `Codable Array/Dictionary/Set` so far.
|
||||
|
||||
- **Optional migration**
|
||||
`Defaults` also provide a migration guide to let users convert them `Codable` things into the UserDefaults native supported type, but it is optional.
|
||||
- [From `Codable` enum in Defaults v4 to `RawRepresentable` in Defaults v5](#from-codable-enum-in-defaults-v4-to-rawrepresentable-in-defaults-v5-optional)
|
||||
- [From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5](#from-codable-struct-in-defaults-v4-to-dictionary-in-defaults-v5-optional)
|
||||
|
||||
## Testing
|
||||
|
||||
We recommend user doing some tests after migration.
|
||||
The most critical issue is the second one (Previous value in UserDefaults is not readable).
|
||||
After migration, there is a need to make sure user can get the same value as before.
|
||||
You can try to test it manually or making a test file to test it.
|
||||
|
||||
Here is the guide for making a migration test:
|
||||
For example you are trying to migrate a `Codable String` array to native array.
|
||||
|
||||
1. Get previous value in UserDefaults (using `defaults` command or whatever you want).
|
||||
|
||||
```swift
|
||||
let string = "[\"a\",\"b\",\"c\"]"
|
||||
```
|
||||
|
||||
2. Insert the value above into UserDefaults.
|
||||
|
||||
```swift
|
||||
UserDefaults.standard.set(string, forKey: "testKey")
|
||||
```
|
||||
|
||||
3. Call `Defaults.migrate` and then using `Defaults` to get its value
|
||||
|
||||
```swift
|
||||
let key = Defaults.Key<[String]>("testKey", default: [])
|
||||
Defaults.migrate(key, to: .v5)
|
||||
|
||||
Defaults[key] //=> [a, b, c]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5
|
||||
|
||||
Before v4, `struct` have to conform to `Codable` to store it as a JSON string.
|
||||
|
||||
After v5, `struct` have to conform to `Defaults.Serializable & Codable` to store it as a JSON string.
|
||||
|
||||
#### Before migration, your code should be like this
|
||||
|
||||
```swift
|
||||
private struct TimeZone: Codable {
|
||||
var id: String
|
||||
var name: String
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let timezone = Defaults.Key<TimeZone?>("TimeZone")
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration steps
|
||||
|
||||
1. Let `TimeZone` conform to `Defaults.Serializable`.
|
||||
|
||||
```swift
|
||||
private struct TimeZone: Defaults.Serializable, Codable {
|
||||
var id: String
|
||||
var name: String
|
||||
}
|
||||
```
|
||||
|
||||
2. Now `Defaults[.timezone]` should be readable.
|
||||
|
||||
---
|
||||
|
||||
### From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5
|
||||
|
||||
Before v4, `enum` have to conform to `Codable` to store it as a JSON string.
|
||||
|
||||
After v5, struct have to conform to `Defaults.Serializable & Codable` to store it as a JSON string.
|
||||
|
||||
#### Before migration, your code should be like this
|
||||
|
||||
```swift
|
||||
private enum Period: String, Codable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let period = Defaults.Key<Period?>("period")
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration steps
|
||||
|
||||
1. Let `Period` conform to `Defaults.Serializable`.
|
||||
|
||||
```swift
|
||||
private enum Period: String, Defaults.Serializable, Codable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
```
|
||||
|
||||
2. Now `Defaults[.period]` should be readable.
|
||||
|
||||
---
|
||||
|
||||
### From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Native Supported Elements) in Defaults v5
|
||||
|
||||
Before v4, `Defaults` will store array/dictionary as a JSON string(`["a", "b", "c"]`).
|
||||
|
||||
After v5, `Defaults` will store it as a native array/dictionary with native supported elements(`[a, b, c]` ).
|
||||
|
||||
#### Before migration, your code should be like this
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let arrayString = Defaults.Key<[String]?>("arrayString")
|
||||
static let setString = Defaults.Key<Set<String>?>("setString")
|
||||
static let dictionaryStringInt = Defaults.Key<[String: Int]?>("dictionaryStringInt")
|
||||
static let dictionaryStringIntInArray = Defaults.Key<[[String: Int]]?>("dictionaryStringIntInArray")
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration steps
|
||||
|
||||
1. **Call `Defaults.migration(.arrayString, to: .v5)`, `Defaults.migration(.setString, to: .v5)`, `Defaults.migration(.dictionaryStringInt, to: .v5)`, `Defaults.migration(.dictionaryStringIntInArray, to: .v5)`.**
|
||||
2. Now `Defaults[.arrayString]`, `Defaults.[.setString]`, `Defaults[.dictionaryStringInt]`, `Defaults[.dictionaryStringIntInArray]` should be readable.
|
||||
|
||||
---
|
||||
|
||||
### From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Codable Elements) in Defaults v5
|
||||
|
||||
Before v4, `Defaults` will store array/dictionary as a JSON string(`"{ "id": "0", "name": "Asia/Taipei" }"`, `"["10 Minutes", "30 Minutes"]"`).
|
||||
|
||||
After v5, `Defaults` will store it as a native array/dictionary with codable elements(`{ id: 0, name: Asia/Taipei }`, `[10 Minutes, 30 Minutes]`).
|
||||
|
||||
#### Before migration, your code should be like this
|
||||
|
||||
```swift
|
||||
private struct TimeZone: Codable, Hashable {
|
||||
var id: String
|
||||
var name: String
|
||||
}
|
||||
private enum Period: String, Codable, Hashable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let arrayTimezone = Defaults.Key<[TimeZone]?>("arrayTimezone")
|
||||
static let setTimezone = Defaults.Key<[TimeZone]?>("setTimezone")
|
||||
static let arrayPeriod = Defaults.Key<[Period]?>("arrayPeriod")
|
||||
static let setPeriod = Defaults.Key<[Period]?>("setPeriod")
|
||||
static let dictionaryTimezone = Defaults.Key<[String: TimeZone]?>("dictionaryTimezone")
|
||||
static let dictionaryPeriod = Defaults.Key<[String: Period]?>("dictionaryPeriod")
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration steps
|
||||
|
||||
1. Let `TimeZone` and `Period` conform to `Defaults.Serializable`
|
||||
|
||||
```swift
|
||||
private struct TimeZone: Defaults.Serializable, Codable, Hashable {
|
||||
var id: String
|
||||
var name: String
|
||||
}
|
||||
|
||||
private enum Period: String, Defaults.Serializable, Codable, Hashable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
```
|
||||
|
||||
2. **Call `Defaults.migration(.arrayTimezone, to: .v5)`, `Defaults.migration(.setTimezone, to: .v5)`, `Defaults.migration(.dictionaryTimezone, to: .v5)`, `Defaults.migration(.arrayPeriod, to: .v5)`, `Defaults.migration(.setPeriod, to: .v5)` , `Defaults.migration(.dictionaryPeriod, to: .v5)`.**
|
||||
3. Now `Defaults[.arrayTimezone]`, `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]`, `Defaults[.arrayPeriod]`, `Defaults[.setPeriod]` , `Defaults[.dictionaryPeriod]` should be readable.
|
||||
|
||||
---
|
||||
|
||||
### From `Codable` enum in Defaults v4 to `RawRepresentable` in Defaults v5 (Optional)
|
||||
|
||||
Before v4, `Defaults` will store `enum` as a JSON string(`"10 Minutes"`).
|
||||
|
||||
After v5, `Defaults` will store `enum` as a `RawRepresentable`(`10 Minutes`).
|
||||
|
||||
#### Before migration, your code should be like this
|
||||
|
||||
```swift
|
||||
private enum Period: String, Codable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let period = Defaults.Key<Period?>("period")
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration steps
|
||||
|
||||
1. Create an enum call `CodablePeriod` and create an extension of it. Let it conform to `Defaults.CodableType` and associated `NativeForm` to `Period`.
|
||||
|
||||
```swift
|
||||
private enum CodablePeriod: String {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
|
||||
extension CodablePeriod: Defaults.CodableType {
|
||||
typealias NativeForm = Period
|
||||
}
|
||||
```
|
||||
|
||||
2. Remove `Codable`. So `Period` can be stored natively.
|
||||
|
||||
```swift
|
||||
private enum Period: String {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
}
|
||||
```
|
||||
|
||||
3. Create an extension of `Period`, let it conform to `Defaults.NativeType` and its `CodableForm` should be `CodablePeriod`.
|
||||
|
||||
```swift
|
||||
extension Period: Defaults.NativeType {
|
||||
typealias CodableForm = CodablePeriod
|
||||
}
|
||||
```
|
||||
|
||||
4. **Call `Defaults.migration(.period)`**
|
||||
5. Now `Defaults[.period]` should be readable.
|
||||
|
||||
* hints: You can also implement `toNative` function at `Defaults.CodableType` in your own way.
|
||||
|
||||
For example
|
||||
|
||||
```swift
|
||||
extension CodablePeriod: Defaults.CodableType {
|
||||
typealias NativeForm = Period
|
||||
|
||||
public func toNative() -> Period {
|
||||
switch self {
|
||||
case .tenMinutes:
|
||||
return .tenMinutes
|
||||
case .halfHour:
|
||||
return .halfHour
|
||||
case .oneHour:
|
||||
return .oneHour
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
### From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5 (Optional)
|
||||
|
||||
This happens when you have a struct which is stored as a codable JSON string before, but now you want it to be stored as a native UserDefaults dictionary.
|
||||
|
||||
#### Before migration, your code should be like this
|
||||
|
||||
```swift
|
||||
private struct TimeZone: Codable {
|
||||
var id: String
|
||||
var name: String
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let timezone = Defaults.Key<TimeZone?>("TimeZone")
|
||||
static let arrayTimezone = Defaults.Key<[TimeZone]?>("arrayTimezone")
|
||||
static let setTimezone = Defaults.Key<Set<TimeZone>?>("setTimezone")
|
||||
static let dictionaryTimezone = Defaults.Key<[String: TimeZone]?>("setTimezone")
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration steps
|
||||
|
||||
1. Create a `TimeZoneBridge` which conform to `Defaults.Bridge` and its `Value` is TimeZone, `Serializable` is `[String: String]`.
|
||||
|
||||
```swift
|
||||
private struct TimeZoneBridge: Defaults.Bridge {
|
||||
typealias Value = TimeZone
|
||||
typealias Serializable = [String: String]
|
||||
|
||||
func serialize(_ value: TimeZone?) -> Serializable? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ["id": value.id, "name": value.name]
|
||||
}
|
||||
|
||||
func deserialize(_ object: Serializable?) -> TimeZone? {
|
||||
guard
|
||||
let dictionary = object,
|
||||
let id = dictionary["id"],
|
||||
let name = dictionary["name"]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return TimeZone(id: id, name: name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Create an extension of `TimeZone`, let it conform to `Defaults.NativeType` and its static bridge is `TimeZoneBridge`(Compiler will complain that `TimeZone` is not conform to `Defaults.NativeType`, will resolve it later).
|
||||
|
||||
```swift
|
||||
private struct TimeZone: Hashable {
|
||||
var id: String
|
||||
var name: String
|
||||
}
|
||||
|
||||
extension TimeZone: Defaults.NativeType {
|
||||
static let bridge = TimeZoneBridge()
|
||||
}
|
||||
```
|
||||
|
||||
3. Create an extension of `CodableTimeZone` and let it conform to `Defaults.CodableType`
|
||||
|
||||
```swift
|
||||
private struct CodableTimeZone {
|
||||
var id: String
|
||||
var name: String
|
||||
}
|
||||
|
||||
extension CodableTimeZone: Defaults.CodableType {
|
||||
/// Convert from `Codable` to `Native`
|
||||
func toNative() -> TimeZone {
|
||||
TimeZone(id: id, name: name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Associate `TimeZone.CodableForm` to `CodableTimeZone`
|
||||
|
||||
```swift
|
||||
extension TimeZone: Defaults.NativeType {
|
||||
/// Associated `CodableForm` to `CodableTimeZone`
|
||||
typealias CodableForm = CodableTimeZone
|
||||
|
||||
static let bridge = TimeZoneBridge()
|
||||
}
|
||||
```
|
||||
|
||||
5. **Call `Defaults.migration(.timezone, to: .v5)`, `Defaults.migration(.arrayTimezone, to: .v5)`, `Defaults.migration(.setTimezone, to: .v5)`, `Defaults.migration(.dictionaryTimezone, to: .v5)`**.
|
||||
6. Now `Defaults[.timezone]`, `Defaults[.arrayTimezone]` , `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]` should be readable.
|
||||
|
||||
**See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.**
|
404
readme.md
404
readme.md
|
@ -19,6 +19,7 @@ For a real-world example, see my [Plash app](https://github.com/sindresorhus/Pla
|
|||
- **Publishers:** Combine publishers built-in.
|
||||
- **Observation:** Observe changes to keys.
|
||||
- **Debuggable:** The data is stored as JSON-serialized values.
|
||||
- **Customizable:** You can serialize and deserialize your own type in your own way.
|
||||
|
||||
## Compatibility
|
||||
|
||||
|
@ -27,6 +28,10 @@ For a real-world example, see my [Plash app](https://github.com/sindresorhus/Pla
|
|||
- tvOS 10+
|
||||
- watchOS 3+
|
||||
|
||||
## Migration Guides
|
||||
|
||||
#### [From v4 to v5](./migration.md)
|
||||
|
||||
## Install
|
||||
|
||||
#### Swift Package Manager
|
||||
|
@ -45,6 +50,29 @@ github "sindresorhus/Defaults"
|
|||
pod 'Defaults'
|
||||
```
|
||||
|
||||
## Support types
|
||||
|
||||
| Single Value |
|
||||
|:------------------:|
|
||||
| `Int(8/16/32/64)` |
|
||||
| `UInt(8/16/32/64)` |
|
||||
| `Double` |
|
||||
| `Float` |
|
||||
| `String` |
|
||||
| `CGFloat` |
|
||||
| `Bool` |
|
||||
| `Date` |
|
||||
| `Data` |
|
||||
| `URL` |
|
||||
| `NSColor` (macOS) |
|
||||
| `UIColor` (iOS) |
|
||||
| `Codable` |
|
||||
|
||||
The list above only show the type that does not need further more configuration.
|
||||
We also support them wrapped in `Array`, `Set`, `Dictionary` even wrapped in nested type. ex. `[[String: Set<[String: Int]>]]`.
|
||||
For more types, see [Enum Example](#enum-example), [Codable Example](#codable-example) or [Advanced Usage](#advanced-usage).
|
||||
For more examples, see [Tests/DefaultsTests](./Tests/DefaultsTests).
|
||||
|
||||
## Usage
|
||||
|
||||
You declare the defaults keys upfront with type and default value.
|
||||
|
@ -92,30 +120,10 @@ The default value is then `nil`.
|
|||
|
||||
---
|
||||
|
||||
If you have `NSSecureCoding` classes which you want to save, you can use them as follows:
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let someSecureCoding = NSSecureCodingKey<SomeNSSecureCodingClass>("someSecureCoding", default: SomeNSSecureCodingClass(string: "Default", int: 5, bool: true))
|
||||
static let someOptionalSecureCoding = NSSecureCodingOptionalKey<Double>("someOptionalSecureCoding")
|
||||
}
|
||||
|
||||
Defaults[.someSecureCoding].string
|
||||
//=> "Default"
|
||||
|
||||
Defaults[.someSecureCoding].int
|
||||
//=> 5
|
||||
|
||||
Defaults[.someSecureCoding].bool
|
||||
//=> true
|
||||
```
|
||||
|
||||
You can use those keys just like in all the other examples. The return value will be your `NSSecureCoding` class.
|
||||
|
||||
### Enum example
|
||||
|
||||
```swift
|
||||
enum DurationKeys: String, Codable {
|
||||
enum DurationKeys: String, Defaults.Serializable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
|
@ -129,6 +137,22 @@ Defaults[.defaultDuration].rawValue
|
|||
//=> "1 Hour"
|
||||
```
|
||||
|
||||
### Codable Example
|
||||
|
||||
```swift
|
||||
struct User: Codable, Defaults.Serializable {
|
||||
let name: String
|
||||
let age: String
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let user = Key<User>("user", default: .init(name: "Hello", age: "24"))
|
||||
}
|
||||
|
||||
Defaults[.user].name
|
||||
//=> "Hello"
|
||||
```
|
||||
|
||||
### Use keys directly
|
||||
|
||||
You are not required to attach keys to `Defaults.Keys`.
|
||||
|
@ -163,8 +187,6 @@ Note that it's `@Default`, not `@Defaults`.
|
|||
|
||||
You cannot use `@Default` in an `ObservableObject`. It's meant to be used in a `View`.
|
||||
|
||||
This is only implemented for `Defaults.Key`. PR welcome for `Defaults.NSSecureCoding` if you need it.
|
||||
|
||||
### Observe changes to a key
|
||||
|
||||
```swift
|
||||
|
@ -317,6 +339,217 @@ print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name))
|
|||
//=> true
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Serialization of custom types
|
||||
|
||||
Although `Defaults` already support many types internal, there might have some situations where you want to use your own type.
|
||||
The guide below will show you how to make your own custom type works with `Defaults`.
|
||||
|
||||
1. Create your own custom type.
|
||||
|
||||
```swift
|
||||
struct User {
|
||||
let name: String
|
||||
let age: String
|
||||
}
|
||||
```
|
||||
|
||||
2. Create a bridge which protocol conforms to `Defaults.Bridge`.
|
||||
|
||||
```swift
|
||||
struct UserBridge: Defaults.Bridge {
|
||||
typealias Value = User
|
||||
typealias Serializable = [String: String]
|
||||
|
||||
public func serialize(_ value: Value?) -> Serializable? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ["name": value.name, "age": value.age]
|
||||
}
|
||||
|
||||
public func deserialize(_ object: Serializable?) -> Value? {
|
||||
guard
|
||||
let object = object,
|
||||
let name = object["name"],
|
||||
let age = object["age"]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return User(name: name, age: age)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Create an extension of `User`, let its protocol conforms to `Defaults.Serializable` and its static bridge should be the bridge we created above.
|
||||
|
||||
```swift
|
||||
struct User {
|
||||
let name: String
|
||||
let age: String
|
||||
}
|
||||
|
||||
extension User: Defaults.Serializable {
|
||||
static let bridge = UserBridge()
|
||||
}
|
||||
```
|
||||
|
||||
4. Create some keys and enjoy it.
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let user = Defaults.Key<User>("user", default: User(name: "Hello", age: "24"))
|
||||
static let arrayUser = Defaults.Key<[User]>("arrayUser", default: [User(name: "Hello", age: "24")])
|
||||
static let setUser = Defaults.Key<Set<User>>("user", default: Set([User(name: "Hello", age: "24")]))
|
||||
static let dictionaryUser = Defaults.Key<[String: User]>("dictionaryUser", default: ["user": User(name: "Hello", age: "24")])
|
||||
}
|
||||
|
||||
Defaults[.user].name //=> "Hello"
|
||||
Defaults[.arrayUser][0].name //=> "Hello"
|
||||
Defaults[.setUser].first?.name //=> "Hello"
|
||||
Defaults[.dictionaryUser]["user"]?.name //=> "Hello"
|
||||
```
|
||||
|
||||
### Serialization of Collection
|
||||
|
||||
1. Create your Collection and its element should conforms to `Defaults.Serializable`.
|
||||
|
||||
```swift
|
||||
struct Bag<Element: Defaults.Serializable>: Collection {
|
||||
var items: [Element]
|
||||
|
||||
var startIndex: Int {
|
||||
items.startIndex
|
||||
}
|
||||
|
||||
var endIndex: Int {
|
||||
items.endIndex
|
||||
}
|
||||
|
||||
mutating func insert(element: Element, at: Int) {
|
||||
items.insert(element, at: at)
|
||||
}
|
||||
|
||||
func index(after index: Int) -> Int {
|
||||
items.index(after: index)
|
||||
}
|
||||
|
||||
subscript(position: Int) -> Element {
|
||||
items[position]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Create an extension of `Bag`. let it conforms to `Defaults.CollectionSerializable`
|
||||
|
||||
```swift
|
||||
extension Bag: Defaults.CollectionSerializable {
|
||||
init(_ elements: [Element]) {
|
||||
self.items = elements
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
3. Create some keys and enjoy it.
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let stringBag = Key<Bag<String>>("stringBag", default: Bag(["Hello", "World!"]))
|
||||
}
|
||||
|
||||
Defaults[.stringBag][0] //=> "Hello"
|
||||
Defaults[.stringBag][1] //=> "World!"
|
||||
```
|
||||
|
||||
### Serialization of SetAlgebra
|
||||
|
||||
1. Create your SetAlgebra and its element should conforms to `Defaults.Serializable & Hashable`
|
||||
|
||||
```swift
|
||||
struct SetBag<Element: Defaults.Serializable & Hashable>: SetAlgebra {
|
||||
var store = Set<Element>()
|
||||
|
||||
init() {}
|
||||
|
||||
init(_ store: Set<Element>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
func contains(_ member: Element) -> Bool {
|
||||
store.contains(member)
|
||||
}
|
||||
|
||||
func union(_ other: SetBag) -> SetBag {
|
||||
SetBag(store.union(other.store))
|
||||
}
|
||||
|
||||
func intersection(_ other: SetBag)
|
||||
-> SetBag {
|
||||
var setBag = SetBag()
|
||||
setBag.store = store.intersection(other.store)
|
||||
return setBag
|
||||
}
|
||||
|
||||
func symmetricDifference(_ other: SetBag)
|
||||
-> SetBag {
|
||||
var setBag = SetBag()
|
||||
setBag.store = store.symmetricDifference(other.store)
|
||||
return setBag
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
mutating func insert(_ newMember: Element)
|
||||
-> (inserted: Bool, memberAfterInsert: Element) {
|
||||
store.insert(newMember)
|
||||
}
|
||||
|
||||
mutating func remove(_ member: Element) -> Element? {
|
||||
store.remove(member)
|
||||
}
|
||||
|
||||
mutating func update(with newMember: Element) -> Element? {
|
||||
store.update(with: newMember)
|
||||
}
|
||||
|
||||
mutating func formUnion(_ other: SetBag) {
|
||||
store.formUnion(other.store)
|
||||
}
|
||||
|
||||
mutating func formSymmetricDifference(_ other: SetBag) {
|
||||
store.formSymmetricDifference(other.store)
|
||||
}
|
||||
|
||||
mutating func formIntersection(_ other: SetBag) {
|
||||
store.formIntersection(other.store)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Create an extension of `SetBag`. Let it conforms to `Defaults.SetAlgebraSerializable`
|
||||
|
||||
```swift
|
||||
extension SetBag: Defaults.SetAlgebraSerializable {
|
||||
func toArray() -> [Element] {
|
||||
Array(store)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Create some keys and enjoy it.
|
||||
|
||||
```swift
|
||||
extension Defaults.Keys {
|
||||
static let stringSet = Key<SetBag<String>>("stringSet", default: SetBag(["Hello", "World!"]))
|
||||
}
|
||||
|
||||
Defaults[.stringSet].contains("Hello") //=> true
|
||||
Defaults[.stringSet].contains("World!") //=> true
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `Defaults`
|
||||
|
@ -327,7 +560,7 @@ Type: `class`
|
|||
|
||||
Stores the keys.
|
||||
|
||||
#### `Defaults.Key` *(alias `Defaults.Keys.Key`)*
|
||||
#### `Defaults.Key` _(alias `Defaults.Keys.Key`)_
|
||||
|
||||
```swift
|
||||
Defaults.Key<T>(_ key: String, default: T, suite: UserDefaults = .standard)
|
||||
|
@ -339,27 +572,48 @@ Create a key with a default value.
|
|||
|
||||
The default value is written to the actual `UserDefaults` and can be used elsewhere. For example, with a Interface Builder binding.
|
||||
|
||||
#### `Defaults.NSSecureCodingKey` *(alias `Defaults.Keys.NSSecureCodingKey`)*
|
||||
#### `Defaults.Serializable`
|
||||
|
||||
```swift
|
||||
Defaults.NSSecureCodingKey<T>(_ key: String, default: T, suite: UserDefaults = .standard)
|
||||
public protocol DefaultsSerializable {
|
||||
typealias Value = Bridge.Value
|
||||
typealias Serializable = Bridge.Serializable
|
||||
associatedtype Bridge: Defaults.Bridge
|
||||
|
||||
static var bridge: Bridge { get }
|
||||
}
|
||||
```
|
||||
|
||||
Type: `class`
|
||||
Type: `protocol`
|
||||
|
||||
Create a NSSecureCoding key with a default value.
|
||||
All types conform to this protocol will be able to work with `Defaults`.
|
||||
|
||||
The default value is written to the actual `UserDefaults` and can be used elsewhere. For example, with a Interface Builder binding.
|
||||
It should have a static variable `bridge` which protocol should conform to `Defaults.Bridge`.
|
||||
|
||||
#### `Defaults.NSSecureCodingOptionalKey` *(alias `Defaults.Keys.NSSecureCodingOptionalKey`)*
|
||||
#### `Defaults.Bridge`
|
||||
|
||||
```swift
|
||||
Defaults.NSSecureCodingOptionalKey<T>(_ key: String, suite: UserDefaults = .standard)
|
||||
public protocol DefaultsBridge {
|
||||
associatedtype Value
|
||||
associatedtype Serializable
|
||||
func serialize(_ value: Value?) -> Serializable?
|
||||
func deserialize(_ object: Serializable?) -> Value?
|
||||
}
|
||||
```
|
||||
|
||||
Type: `class`
|
||||
Type: `protocol`
|
||||
|
||||
Create a NSSecureCoding key with an optional value.
|
||||
A Bridge can do serialization and de-serialization.
|
||||
|
||||
Have two associate types `Value` and `Serializable`.
|
||||
|
||||
`Value` is the type user want to use it.
|
||||
|
||||
`Serializable` is the type stored in `UserDefaults`.
|
||||
|
||||
`serialize` will be executed before storing to the `UserDefaults` .
|
||||
|
||||
`deserialize` will be executed after retrieving its value from the `UserDefaults`.
|
||||
|
||||
#### `Defaults.reset(keys…)`
|
||||
|
||||
|
@ -381,22 +635,6 @@ Defaults.observe<T: Codable>(
|
|||
) -> Defaults.Observation
|
||||
```
|
||||
|
||||
```swift
|
||||
Defaults.observe<T: NSSecureCoding>(
|
||||
_ key: Defaults.NSSecureCodingKey<T>,
|
||||
options: ObservationOptions = [.initial],
|
||||
handler: @escaping (NSSecureCodingKeyChange<T>) -> Void
|
||||
) -> Defaults.Observation
|
||||
```
|
||||
|
||||
```swift
|
||||
Defaults.observe<T: NSSecureCoding>(
|
||||
_ key: Defaults.NSSecureCodingOptionalKey<T>,
|
||||
options: ObservationOptions = [.initial],
|
||||
handler: @escaping (NSSecureCodingOptionalKeyChange<T>) -> Void
|
||||
) -> Defaults.Observation
|
||||
```
|
||||
|
||||
Type: `func`
|
||||
|
||||
Observe changes to a key or an optional key.
|
||||
|
@ -420,20 +658,6 @@ Defaults.publisher<T: Codable>(
|
|||
) -> AnyPublisher<KeyChange<T>, Never>
|
||||
```
|
||||
|
||||
```swift
|
||||
Defaults.publisher<T: NSSecureCoding>(
|
||||
_ key: Defaults.NSSecureCodingKey<T>,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<NSSecureCodingKeyChange<T>, Never>
|
||||
```
|
||||
|
||||
```swift
|
||||
Defaults.publisher<T: NSSecureCoding>(
|
||||
_ key: Defaults.NSSecureCodingOptionalKey<T>,
|
||||
options: ObservationOptions = [.initial]
|
||||
) -> AnyPublisher<NSSecureCodingOptionalKeyChange<T>, Never>
|
||||
```
|
||||
|
||||
Type: `func`
|
||||
|
||||
Observation API using [Publisher](https://developer.apple.com/documentation/combine/publisher) from the [Combine](https://developer.apple.com/documentation/combine) framework.
|
||||
|
@ -505,21 +729,71 @@ Execute the closure without triggering change events.
|
|||
|
||||
Any `Defaults` key changes made within the closure will not propagate to `Defaults` event listeners (`Defaults.observe()` and `Defaults.publisher()`). This can be useful to prevent infinite recursion when you want to change a key in the callback listening to changes for the same key.
|
||||
|
||||
#### `Defaults.migrate(keys..., to: Version)`
|
||||
|
||||
```swift
|
||||
Defaults.migrate<T: Defaults.Serializable & Codable>(keys..., to: Version)
|
||||
Defaults.migrate<T: Defaults.NativeType>(keys..., to: Version)
|
||||
```
|
||||
|
||||
Type: `func`
|
||||
|
||||
Migrate the given keys to the specific version.
|
||||
|
||||
You can specify up to 10 keys. If you need to specify more, call this method multiple times.
|
||||
|
||||
### `@Default(_ key:)`
|
||||
|
||||
Get/set a `Defaults` item and also have the view be updated when the value changes.
|
||||
|
||||
This is only implemented for `Defaults.Key`. PR welcome for `Defaults.NSSecureCoding` if you need it.
|
||||
### Advanced
|
||||
|
||||
#### `Defaults.CollectionSerializable`
|
||||
|
||||
```swift
|
||||
public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable {
|
||||
init(_ elements: [Element])
|
||||
}
|
||||
```
|
||||
|
||||
Type: `protocol`
|
||||
|
||||
A `Collection` which can store into the native `UserDefaults`.
|
||||
|
||||
It should have an initializer `init(_ elements: [Element])` to let `Defaults` do the de-serialization.
|
||||
|
||||
#### `Defaults.SetAlgebraSerializable`
|
||||
|
||||
```swift
|
||||
public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializable {
|
||||
func toArray() -> [Element]
|
||||
}
|
||||
```
|
||||
|
||||
Type: `protocol`
|
||||
|
||||
A `SetAlgebra` which can store into the native `UserDefaults`.
|
||||
|
||||
It should have a function `func toArray() -> [Element]` to let `Defaults` do the serialization.
|
||||
|
||||
## FAQ
|
||||
|
||||
### How can I store a dictionary of arbitrary values?
|
||||
|
||||
You cannot store `[String: Any]` directly as it cannot conform to `Codable`. However, you can use the [`AnyCodable`](https://github.com/Flight-School/AnyCodable) package to work around this `Codable` limitation:
|
||||
After `Defaults` v5, you don't need to use `Codable` to store dictionary, `Defaults` supports storing dictionary natively.
|
||||
For `Defaults` support types, see [Support types](#support-types).
|
||||
|
||||
There might be situations where you want to use `[String: Any]` directly.
|
||||
Unfortunately, since `Any` can not conform to `Defaults.Serializable`, `Defaults` can not support it.
|
||||
|
||||
However, you can use the [`AnyCodable`](https://github.com/Flight-School/AnyCodable) package to work around this `Defaults.Serializable` limitation:
|
||||
|
||||
```swift
|
||||
import AnyCodable
|
||||
|
||||
/// Important: Let AnyCodable conforms to Defaults.Serializable
|
||||
extension AnyCodable: Defaults.Serializable {}
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let magic = Key<[String: AnyCodable]>("magic", default: [:])
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue