← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit 01c629a9b1aa3ff44f7f77e263df79a492d03953
parent 6461f078965680b59f9fd7ffba514062116ddad6
Author: Michael Camilleri <[email protected]>
Date:   Sun,  8 Feb 2026 00:11:43 +0900

Add unit tests

Co-Authored-By: Claude 4.5 Sonnet <[email protected]>

Diffstat

MListless.xcodeproj/project.pbxproj | 208++++++++++++++++++++++++++++++++++++++++++-------------------------------------
MListless.xcodeproj/xcshareddata/xcschemes/Listless iOS.xcscheme | 14++++++++++++++
MListless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme | 13-------------
MListless/Models/TaskStore.swift | 24++++++++++++++++--------
MListlessiOS/Views/ClickableTextField.swift | 15++++++++++++---
ATests/Support/TestHelpers.swift | 37+++++++++++++++++++++++++++++++++++++
ATests/Unit/TaskStoreCompletionTests.swift | 148++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATests/Unit/TaskStoreEdgeCaseTests.swift | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATests/Unit/TaskStoreOrderingTests.swift | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATests/Unit/TaskStoreTests.swift | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

10 files changed, 876 insertions(+), 121 deletions(-)

diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj

@@ -7,12 +7,12 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		031B814FDF3F9B20E3602563 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 992AE558C5BBEE16D70B8FF4 /* README.md */; };
 		0ACA67F6578EFF181EE5C9A7 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
 		15B71073767FB4766A6BA2BE /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */; };
+		1AA328A921EF8A7FDD03119A /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B048B19C5219862BBED2E7 /* TestHelpers.swift */; };
+		269B93D5543770B464DFB37A /* TaskStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */; };
 		3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
 		42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; };
-		585A9E665730E3C4FD32AE8D /* KeyboardNavigationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F99C67C30185163A51DCD268 /* KeyboardNavigationTests.swift */; };
 		5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
 		614FCCA450EC0BFFD8B40640 /* ListlessMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */; };
 		6C252050E62AED3A0A684EBF /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */; };
@@ -23,6 +23,7 @@
 		91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
 		96617677059FABDBB80D642B /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
 		978DABFA617B6EBD6FA179CD /* ClickableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A08C589AB65ED2CB26A092 /* ClickableTextField.swift */; };
+		99977BFA37FBAAA49AF6B71E /* TaskStoreEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */; };
 		A0AA8FD4C542E9AEB2437BC2 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
 		A8D13DD6196772ECA146CF2A /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D74AE2501F35974F57D21F /* PlatformScrollIndicatorsModifier.swift */; };
 		C1BD61D6DFA687E9CB9ACA60 /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138612F350ECE29526F689B9 /* TaskRowDragGesture.swift */; };
@@ -33,17 +34,19 @@
 		D8EFF49E9156083D675D47F0 /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */; };
 		DC71C45D92524C3893BF9FDB /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */; };
 		DC73A39A269AB495BCE1AC48 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
+		ECD5E7EA05AE1C00B38C939E /* TaskStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */; };
 		F0B2B806BD84A4F2FDF8E038 /* ListlessiOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC245331D715EA85887C0BA0 /* ListlessiOSApp.swift */; };
+		F50E8B8F73F64D8E641DC74C /* TaskStoreCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */; };
 		FEC96DAA2BF8BB5D6D8504EB /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
-		CBF0AD7D47832BDE6A4FB05B /* PBXContainerItemProxy */ = {
+		6F7E207AE10E1516EF8A683C /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = 3256C2BF8F1DAF371DA32120 /* Project object */;
 			proxyType = 1;
-			remoteGlobalIDString = 0FB4F07A37999BBC6DFE4DBB;
-			remoteInfo = "Listless macOS";
+			remoteGlobalIDString = 34A03D42B91730DEAC2EBD8E;
+			remoteInfo = "Listless iOS";
 		};
 /* End PBXContainerItemProxy section */
 
@@ -56,29 +59,34 @@
 		138612F350ECE29526F689B9 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
 		1B3D0B4710C7A0EE4F227851 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
 		1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessMacApp.swift; sourceTree = "<group>"; };
+		2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreOrderingTests.swift; sourceTree = "<group>"; };
 		3313FEDB101EECA4B344EEF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
-		3F0FC3AFB591F222FB63C13B /* Listless macOS UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Listless macOS UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
+		4FC64B9F9370041BEDBD1E14 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		537A913AC421BAEF60D26D9C /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
 		5B0E22B8F7B2B7283CAF749E /* Listless macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Listless macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreCompletionTests.swift; sourceTree = "<group>"; };
 		74255E6B6C40899E9B17D927 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
+		75B048B19C5219862BBED2E7 /* TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = "<group>"; };
 		7C73E9D4C42CCABBF0F33543 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
 		81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
 		82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
 		9262207DAC21619BD9EDEE15 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
 		944BAE054AAC1B9C4FC954F9 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
+		967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreTests.swift; sourceTree = "<group>"; };
 		97A08C589AB65ED2CB26A092 /* ClickableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickableTextField.swift; sourceTree = "<group>"; };
-		992AE558C5BBEE16D70B8FF4 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
+		9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreEdgeCaseTests.swift; sourceTree = "<group>"; };
 		9FBEB58DD41817F09B0EB9F0 /* Listless.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Listless.xcdatamodel; sourceTree = "<group>"; };
 		AC245331D715EA85887C0BA0 /* ListlessiOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessiOSApp.swift; sourceTree = "<group>"; };
 		B4D74AE2501F35974F57D21F /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
 		C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
+		C71466C5CD1A5BA984352F8D /* Listless iOS Unit Tests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = "Listless iOS Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+		C9B14DC786A336008AAB78EE /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		D10B5491A53E77C80F8F75CD /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		D123BB181208FC825777B0A7 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
 		DC3DEE364304587D280C5672 /* TaskStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStore.swift; sourceTree = "<group>"; };
 		EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
-		F99C67C30185163A51DCD268 /* KeyboardNavigationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardNavigationTests.swift; sourceTree = "<group>"; };
 		FBB8A3BEB346267B30B4675F /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -97,8 +105,8 @@
 		3936BDEE64D16E6C4C85B3DD /* Products */ = {
 			isa = PBXGroup;
 			children = (
+				C71466C5CD1A5BA984352F8D /* Listless iOS Unit Tests.xctest */,
 				126108860D7878DDC3BECC4B /* Listless iOS.app */,
-				3F0FC3AFB591F222FB63C13B /* Listless macOS UITests.xctest */,
 				5B0E22B8F7B2B7283CAF749E /* Listless macOS.app */,
 			);
 			name = Products;
@@ -149,15 +157,6 @@
 			path = ListlessiOS;
 			sourceTree = "<group>";
 		};
-		A4CAFE5C963E75C444972E10 /* ListlessMacUITests */ = {
-			isa = PBXGroup;
-			children = (
-				F99C67C30185163A51DCD268 /* KeyboardNavigationTests.swift */,
-				992AE558C5BBEE16D70B8FF4 /* README.md */,
-			);
-			path = ListlessMacUITests;
-			sourceTree = "<group>";
-		};
 		AA563E991F69ED14DCD9A1AB /* Infrastructure */ = {
 			isa = PBXGroup;
 			children = (
@@ -189,6 +188,16 @@
 			path = ListlessMac;
 			sourceTree = "<group>";
 		};
+		D98A7EE79F8E29297555B801 /* Support */ = {
+			isa = PBXGroup;
+			children = (
+				4FC64B9F9370041BEDBD1E14 /* .gitkeep */,
+				75B048B19C5219862BBED2E7 /* TestHelpers.swift */,
+			);
+			name = Support;
+			path = Tests/Support;
+			sourceTree = "<group>";
+		};
 		E98EF3B4638A9E8473EA62FA /* Sync */ = {
 			isa = PBXGroup;
 			children = (
@@ -204,11 +213,25 @@
 				58051CBDE2390F9E13647235 /* Listless */,
 				954AAB8DDFF6E6D6FD6A0A2C /* ListlessiOS */,
 				D5B197AFF26144948D032299 /* ListlessMac */,
-				A4CAFE5C963E75C444972E10 /* ListlessMacUITests */,
+				D98A7EE79F8E29297555B801 /* Support */,
+				F63E6E98ABFB43E1B32686B4 /* Unit */,
 				3936BDEE64D16E6C4C85B3DD /* Products */,
 			);
 			sourceTree = "<group>";
 		};
+		F63E6E98ABFB43E1B32686B4 /* Unit */ = {
+			isa = PBXGroup;
+			children = (
+				C9B14DC786A336008AAB78EE /* .gitkeep */,
+				5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */,
+				9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */,
+				2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */,
+				967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */,
+			);
+			name = Unit;
+			path = Tests/Unit;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -246,24 +269,23 @@
 			productReference = 126108860D7878DDC3BECC4B /* Listless iOS.app */;
 			productType = "com.apple.product-type.application";
 		};
-		4DDF5F8F5508AA35A41BECAB /* Listless macOS UITests */ = {
+		D533FDEDCE6DFCA2E8CB70F5 /* Listless iOS Unit Tests */ = {
 			isa = PBXNativeTarget;
-			buildConfigurationList = 9FF482EB568895AF0B3F35CC /* Build configuration list for PBXNativeTarget "Listless macOS UITests" */;
+			buildConfigurationList = 1993E9E4443DC6AB506DE24C /* Build configuration list for PBXNativeTarget "Listless iOS Unit Tests" */;
 			buildPhases = (
-				34DA2E4A88D1AFCE78FCE279 /* Sources */,
-				ED7DBE75C614608E5D3B630F /* Resources */,
+				D959A967B1BB3E9246C006D7 /* Sources */,
 			);
 			buildRules = (
 			);
 			dependencies = (
-				180D7333E25210955007E660 /* PBXTargetDependency */,
+				03DCFDF6E769DBF0DBC470F6 /* PBXTargetDependency */,
 			);
-			name = "Listless macOS UITests";
+			name = "Listless iOS Unit Tests";
 			packageProductDependencies = (
 			);
-			productName = "Listless macOS UITests";
-			productReference = 3F0FC3AFB591F222FB63C13B /* Listless macOS UITests.xctest */;
-			productType = "com.apple.product-type.bundle.ui-testing";
+			productName = "Listless iOS Unit Tests";
+			productReference = C71466C5CD1A5BA984352F8D /* Listless iOS Unit Tests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
 		};
 /* End PBXNativeTarget section */
 
@@ -282,10 +304,9 @@
 						DevelopmentTeam = 7TD7PZBNXP;
 						ProvisioningStyle = Automatic;
 					};
-					4DDF5F8F5508AA35A41BECAB = {
+					D533FDEDCE6DFCA2E8CB70F5 = {
 						DevelopmentTeam = 7TD7PZBNXP;
 						ProvisioningStyle = Automatic;
-						TestTargetID = 0FB4F07A37999BBC6DFE4DBB;
 					};
 				};
 			};
@@ -304,32 +325,13 @@
 			projectRoot = "";
 			targets = (
 				34A03D42B91730DEAC2EBD8E /* Listless iOS */,
+				D533FDEDCE6DFCA2E8CB70F5 /* Listless iOS Unit Tests */,
 				0FB4F07A37999BBC6DFE4DBB /* Listless macOS */,
-				4DDF5F8F5508AA35A41BECAB /* Listless macOS UITests */,
 			);
 		};
 /* End PBXProject section */
 
-/* Begin PBXResourcesBuildPhase section */
-		ED7DBE75C614608E5D3B630F /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				031B814FDF3F9B20E3602563 /* README.md in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXResourcesBuildPhase section */
-
 /* Begin PBXSourcesBuildPhase section */
-		34DA2E4A88D1AFCE78FCE279 /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				585A9E665730E3C4FD32AE8D /* KeyboardNavigationTests.swift in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 		409D108909CBEC2F69B56D0E /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -370,36 +372,29 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		D959A967B1BB3E9246C006D7 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				F50E8B8F73F64D8E641DC74C /* TaskStoreCompletionTests.swift in Sources */,
+				99977BFA37FBAAA49AF6B71E /* TaskStoreEdgeCaseTests.swift in Sources */,
+				269B93D5543770B464DFB37A /* TaskStoreOrderingTests.swift in Sources */,
+				ECD5E7EA05AE1C00B38C939E /* TaskStoreTests.swift in Sources */,
+				1AA328A921EF8A7FDD03119A /* TestHelpers.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
-		180D7333E25210955007E660 /* PBXTargetDependency */ = {
+		03DCFDF6E769DBF0DBC470F6 /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
-			target = 0FB4F07A37999BBC6DFE4DBB /* Listless macOS */;
-			targetProxy = CBF0AD7D47832BDE6A4FB05B /* PBXContainerItemProxy */;
+			target = 34A03D42B91730DEAC2EBD8E /* Listless iOS */;
+			targetProxy = 6F7E207AE10E1516EF8A683C /* PBXContainerItemProxy */;
 		};
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */
-		086CB8FDA1EC9D4A988C382E /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				BUNDLE_LOADER = "$(TEST_HOST)";
-				CODE_SIGN_STYLE = Automatic;
-				COMBINE_HIDPI_IMAGES = YES;
-				DEVELOPMENT_TEAM = 7TD7PZBNXP;
-				GENERATE_INFOPLIST_FILE = YES;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-					"@loader_path/Frameworks",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.mac.uitests;
-				SDKROOT = macosx;
-				TEST_TARGET_NAME = "Listless macOS";
-			};
-			name = Release;
-		};
 		2D4E3CC5FF8E6299F754CCFC /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -418,25 +413,6 @@
 			};
 			name = Debug;
 		};
-		31AB47694981749AC7C9E541 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				BUNDLE_LOADER = "$(TEST_HOST)";
-				CODE_SIGN_STYLE = Automatic;
-				COMBINE_HIDPI_IMAGES = YES;
-				DEVELOPMENT_TEAM = 7TD7PZBNXP;
-				GENERATE_INFOPLIST_FILE = YES;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-					"@loader_path/Frameworks",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.mac.uitests;
-				SDKROOT = macosx;
-				TEST_TARGET_NAME = "Listless macOS";
-			};
-			name = Debug;
-		};
 		A5A377EA0FE470803E2B6BA1 /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -511,6 +487,25 @@
 			};
 			name = Release;
 		};
+		C00090C364E4A0B81DC878F0 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
+				GENERATE_INFOPLIST_FILE = YES;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.ios.unittests;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Listless iOS.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Listless iOS";
+			};
+			name = Debug;
+		};
 		D1B900A3E25ED5B39F0D9716 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -593,6 +588,25 @@
 			};
 			name = Debug;
 		};
+		F32B53087C3BB2F10323145F /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
+				GENERATE_INFOPLIST_FILE = YES;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.ios.unittests;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Listless iOS.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Listless iOS";
+			};
+			name = Release;
+		};
 		F9DCF7B07CCBB6881C4642BD /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -615,20 +629,20 @@
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
-		4D46AAEA40BF3A5A8C4E1999 /* Build configuration list for PBXNativeTarget "Listless macOS" */ = {
+		1993E9E4443DC6AB506DE24C /* Build configuration list for PBXNativeTarget "Listless iOS Unit Tests" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
-				2D4E3CC5FF8E6299F754CCFC /* Debug */,
-				A5A377EA0FE470803E2B6BA1 /* Release */,
+				C00090C364E4A0B81DC878F0 /* Debug */,
+				F32B53087C3BB2F10323145F /* Release */,
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Debug;
 		};
-		9FF482EB568895AF0B3F35CC /* Build configuration list for PBXNativeTarget "Listless macOS UITests" */ = {
+		4D46AAEA40BF3A5A8C4E1999 /* Build configuration list for PBXNativeTarget "Listless macOS" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
-				31AB47694981749AC7C9E541 /* Debug */,
-				086CB8FDA1EC9D4A988C382E /* Release */,
+				2D4E3CC5FF8E6299F754CCFC /* Debug */,
+				A5A377EA0FE470803E2B6BA1 /* Release */,
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Debug;

diff --git a/Listless.xcodeproj/xcshareddata/xcschemes/Listless iOS.xcscheme b/Listless.xcodeproj/xcshareddata/xcschemes/Listless iOS.xcscheme

@@ -39,7 +39,21 @@
          </BuildableReference>
       </MacroExpansion>
       <Testables>
+         <TestableReference
+            skipped = "NO"
+            parallelizable = "YES"
+            testExecutionOrdering = "random">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "D533FDEDCE6DFCA2E8CB70F5"
+               BuildableName = "Listless iOS Unit Tests.xctest"
+               BlueprintName = "Listless iOS Unit Tests"
+               ReferencedContainer = "container:Listless.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
       </Testables>
+      <CommandLineArguments>
+      </CommandLineArguments>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"

diff --git a/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme b/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme

@@ -39,20 +39,7 @@
          </BuildableReference>
       </MacroExpansion>
       <Testables>
-         <TestableReference
-            skipped = "NO"
-            parallelizable = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "4DDF5F8F5508AA35A41BECAB"
-               BuildableName = "Listless macOS UITests.xctest"
-               BlueprintName = "Listless macOS UITests"
-               ReferencedContainer = "container:Listless.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
       </Testables>
-      <CommandLineArguments>
-      </CommandLineArguments>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"

diff --git a/Listless/Models/TaskStore.swift b/Listless/Models/TaskStore.swift

@@ -16,13 +16,20 @@ final class TaskStore {
 
     func fetchTasks() -> [TaskItem] {
         let request = TaskItem.fetchRequest()
-        request.sortDescriptors = [
-            NSSortDescriptor(keyPath: \TaskItem.isCompleted, ascending: true),
-            NSSortDescriptor(keyPath: \TaskItem.createdAt, ascending: true),
-        ]
+        request.sortDescriptors = []
 
         do {
-            return try context.fetch(request)
+            let allTasks = try context.fetch(request)
+
+            // Active tasks sorted by sortOrder
+            let activeTasks = allTasks.filter { !$0.isCompleted }
+                .sorted { $0.sortOrder < $1.sortOrder }
+
+            // Completed tasks sorted by updatedAt (most recently completed last)
+            let completedTasks = allTasks.filter { $0.isCompleted }
+                .sorted { $0.updatedAt < $1.updatedAt }
+
+            return activeTasks + completedTasks
         } catch {
             print("Failed to fetch tasks: \(error)")
             return []
@@ -34,8 +41,9 @@ final class TaskStore {
         task.title = title
 
         // Set sortOrder to end of active tasks
+        context.processPendingChanges()
         let activeTasks = fetchTasks().filter { !$0.isCompleted }
-        let maxOrder = activeTasks.map(\.sortOrder).max() ?? 0
+        let maxOrder = activeTasks.map(\.sortOrder).max() ?? -1000
         task.sortOrder = maxOrder + 1000
 
         save()
@@ -82,8 +90,8 @@ final class TaskStore {
         var reordered = activeTasks
         let task = reordered.remove(at: currentIndex)
 
-        // Clamp toIndex to valid range after removal
-        let insertIndex = min(toIndex, reordered.count)
+        // Clamp toIndex to valid range [0, reordered.count] after removal
+        let insertIndex = max(0, min(toIndex, reordered.count))
         reordered.insert(task, at: insertIndex)
 
         // Reassign sortOrder with gaps of 1000

diff --git a/ListlessiOS/Views/ClickableTextField.swift b/ListlessiOS/Views/ClickableTextField.swift

@@ -4,9 +4,10 @@ import SwiftUI
 struct ClickableTextField: View {
     @Binding var text: String
     let isCompleted: Bool
-    let onEditingChanged: (Bool) -> Void
+    let onEditingChanged: (Bool, _ shouldCreateNewTask: Bool) -> Void
 
     @FocusState private var isFocused: Bool
+    @State private var submittedViaReturn = false
 
     var body: some View {
         TextField("New task", text: $text, axis: .vertical)
@@ -15,12 +16,20 @@ struct ClickableTextField: View {
             .lineLimit(1...5)
             .focused($isFocused)
             .onSubmit {
-                // Resign focus when Return is pressed (same as focus loss)
+                // Return key pressed - mark for new task creation
+                submittedViaReturn = true
                 isFocused = false
             }
             .disabled(isCompleted)
             .onChange(of: isFocused) { _, newValue in
-                onEditingChanged(newValue)
+                if newValue {
+                    // Focus gained
+                    onEditingChanged(true, false)
+                } else {
+                    // Focus lost - check if it was via Return key
+                    onEditingChanged(false, submittedViaReturn)
+                    submittedViaReturn = false
+                }
             }
     }
 }

diff --git a/Tests/Support/TestHelpers.swift b/Tests/Support/TestHelpers.swift

@@ -0,0 +1,37 @@
+import Foundation
+import Testing
+
+@testable import Listless_iOS
+
+/// Creates a fresh TaskStore with in-memory persistence for isolated testing.
+@MainActor
+func makeTestStore() -> TaskStore {
+    let controller = PersistenceController(inMemory: true)
+    return TaskStore(persistenceController: controller)
+}
+
+/// Creates a TaskStore pre-populated with test tasks.
+/// - Parameters:
+///   - count: Number of tasks to create (default: 3)
+///   - titles: Optional array of titles; if nil, generates "Task 1", "Task 2", etc.
+/// - Returns: Tuple of (store, array of created task IDs)
+@MainActor
+func makeTestStoreWithTasks(count: Int = 3, titles: [String]? = nil) -> (TaskStore, [UUID]) {
+    let store = makeTestStore()
+    var taskIDs: [UUID] = []
+
+    for i in 0..<count {
+        let title = titles?[safe: i] ?? "Task \(i + 1)"
+        let task = store.createTask(title: title)
+        taskIDs.append(task.id)
+    }
+
+    return (store, taskIDs)
+}
+
+/// Safe array subscript that returns nil instead of crashing on out-of-bounds access.
+extension Array {
+    subscript(safe index: Int) -> Element? {
+        return indices.contains(index) ? self[index] : nil
+    }
+}

diff --git a/Tests/Unit/TaskStoreCompletionTests.swift b/Tests/Unit/TaskStoreCompletionTests.swift

@@ -0,0 +1,148 @@
+import Foundation
+import Testing
+
+@testable import Listless_iOS
+
+@Suite("TaskStore Completion Behavior", .serialized)
+@MainActor
+struct TaskStoreCompletionTests {
+
+    // MARK: - Basic Completion Tests
+
+    @Test("Complete task")
+    func completeTask() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Task to complete")
+
+        store.complete(taskID: task.id)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.isCompleted == true)
+    }
+
+    @Test("Uncomplete task")
+    func uncompleteTask() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Task")
+        store.complete(taskID: task.id)
+
+        store.uncomplete(taskID: task.id)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.isCompleted == false)
+    }
+
+    @Test("Complete with invalid ID does nothing")
+    func completeWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Task")
+        let invalidID = UUID()
+
+        store.complete(taskID: invalidID)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.isCompleted == false)
+    }
+
+    @Test("Uncomplete with invalid ID does nothing")
+    func uncompleteWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Task")
+        store.complete(taskID: task.id)
+        let invalidID = UUID()
+
+        store.uncomplete(taskID: invalidID)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.isCompleted == true)
+    }
+
+    // MARK: - Timestamp Tests
+
+    @Test("Completing task updates timestamp")
+    func completingTaskUpdatesTimestamp() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Task")
+        let originalUpdatedAt = task.updatedAt
+
+        // Small delay to ensure timestamp difference
+        try await Task.sleep(nanoseconds: 10_000_000) // 10ms
+
+        store.complete(taskID: task.id)
+
+        let tasks = store.fetchTasks()
+        let updatedTask = tasks.first
+        #expect(updatedTask?.updatedAt ?? Date() > originalUpdatedAt)
+    }
+
+    // MARK: - Sorting Tests
+
+    @Test("Active tasks appear before completed tasks")
+    func activeTasksAppearBeforeCompletedTasks() async throws {
+        let store = makeTestStore()
+        let task1 = store.createTask(title: "Task 1")
+        let task2 = store.createTask(title: "Task 2")
+        let task3 = store.createTask(title: "Task 3")
+
+        store.complete(taskID: task2.id)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks[0].id == task1.id)
+        #expect(tasks[1].id == task3.id)
+        #expect(tasks[2].id == task2.id)
+    }
+
+    @Test("Completed tasks sorted by updatedAt")
+    func completedTasksSortedByUpdatedAt() async throws {
+        let store = makeTestStore()
+        let task1 = store.createTask(title: "Task 1")
+        let task2 = store.createTask(title: "Task 2")
+        let task3 = store.createTask(title: "Task 3")
+
+        // Complete in specific order with delays
+        store.complete(taskID: task2.id)
+        try await Task.sleep(nanoseconds: 10_000_000) // 10ms
+
+        store.complete(taskID: task1.id)
+        try await Task.sleep(nanoseconds: 10_000_000) // 10ms
+
+        store.complete(taskID: task3.id)
+
+        let tasks = store.fetchTasks()
+        // All completed, should be sorted by updatedAt (completion order)
+        #expect(tasks[0].id == task2.id)
+        #expect(tasks[1].id == task1.id)
+        #expect(tasks[2].id == task3.id)
+    }
+
+    @Test("Toggle completion multiple times")
+    func toggleCompletionMultipleTimes() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Task")
+
+        store.complete(taskID: task.id)
+        var tasks = store.fetchTasks()
+        #expect(tasks.first?.isCompleted == true)
+
+        store.uncomplete(taskID: task.id)
+        tasks = store.fetchTasks()
+        #expect(tasks.first?.isCompleted == false)
+
+        store.complete(taskID: task.id)
+        tasks = store.fetchTasks()
+        #expect(tasks.first?.isCompleted == true)
+    }
+
+    @Test("Complete all tasks")
+    func completeAllTasks() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 5)
+
+        for id in taskIDs {
+            store.complete(taskID: id)
+        }
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.allSatisfy { $0.isCompleted })
+        #expect(tasks.count == 5)
+    }
+}

diff --git a/Tests/Unit/TaskStoreEdgeCaseTests.swift b/Tests/Unit/TaskStoreEdgeCaseTests.swift

@@ -0,0 +1,163 @@
+import Foundation
+import Testing
+
+@testable import Listless_iOS
+
+@Suite("TaskStore Edge Cases", .serialized)
+@MainActor
+struct TaskStoreEdgeCaseTests {
+
+    // MARK: - Title Edge Cases
+
+    @Test("Task with empty title")
+    func taskWithEmptyTitle() async throws {
+        let store = makeTestStore()
+
+        let task = store.createTask(title: "")
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.title == "")
+    }
+
+    @Test("Task with very long title")
+    func taskWithVeryLongTitle() async throws {
+        let store = makeTestStore()
+        let longTitle = String(repeating: "A", count: 10_000)
+
+        let task = store.createTask(title: longTitle)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.title.count == 10_000)
+    }
+
+    @Test("Task with special characters")
+    func taskWithSpecialCharacters() async throws {
+        let store = makeTestStore()
+        let specialTitle = "Test 🎉 with émojis & spëcial çharacters! @#$%^&*()"
+
+        let task = store.createTask(title: specialTitle)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.title == specialTitle)
+    }
+
+    @Test("Task with newlines and tabs")
+    func taskWithNewlinesAndTabs() async throws {
+        let store = makeTestStore()
+        let multilineTitle = "Line 1\nLine 2\tTabbed"
+
+        let task = store.createTask(title: multilineTitle)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.title == multilineTitle)
+    }
+
+    // MARK: - Large Data Sets
+
+    @Test("Create many tasks")
+    func createManyTasks() async throws {
+        let store = makeTestStore()
+        let count = 100
+
+        for i in 0..<count {
+            _ = store.createTask(title: "Task \(i)")
+        }
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.count == count)
+    }
+
+    @Test("Delete all tasks from large set")
+    func deleteAllTasksFromLargeSet() async throws {
+        let store = makeTestStore()
+        var taskIDs: [UUID] = []
+
+        for i in 0..<50 {
+            let task = store.createTask(title: "Task \(i)")
+            taskIDs.append(task.id)
+        }
+
+        for id in taskIDs {
+            store.delete(taskID: id)
+        }
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.isEmpty)
+    }
+
+    // MARK: - State Transitions
+
+    @Test("Create task after completing all tasks")
+    func createTaskAfterCompletingAllTasks() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+
+        for id in taskIDs {
+            store.complete(taskID: id)
+        }
+
+        let newTask = store.createTask(title: "New task")
+
+        let tasks = store.fetchTasks()
+        let activeTasks = tasks.filter { !$0.isCompleted }
+        #expect(activeTasks.count == 1)
+        #expect(activeTasks[0].id == newTask.id)
+    }
+
+    @Test("Rapid updates to same task")
+    func rapidUpdatesToSameTask() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Original")
+
+        for i in 0..<10 {
+            store.update(taskID: task.id, title: "Update \(i)")
+        }
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.title == "Update 9")
+    }
+
+    // MARK: - Store State Tests
+
+    @Test("Store with only completed tasks")
+    func storeWithOnlyCompletedTasks() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 5)
+
+        for id in taskIDs {
+            store.complete(taskID: id)
+        }
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.allSatisfy { $0.isCompleted })
+        #expect(tasks.count == 5)
+    }
+
+    @Test("SortOrder after completing all tasks")
+    func sortOrderAfterCompletingAllTasks() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+
+        for id in taskIDs {
+            store.complete(taskID: id)
+        }
+
+        let newTask1 = store.createTask(title: "New 1")
+        let newTask2 = store.createTask(title: "New 2")
+
+        let activeTasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(activeTasks[0].id == newTask1.id)
+        #expect(activeTasks[1].id == newTask2.id)
+        #expect(activeTasks[1].sortOrder > activeTasks[0].sortOrder)
+    }
+
+    @Test("Uncompleting task moves it back to active")
+    func uncompletingTaskMovesItBackToActive() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+        store.complete(taskID: taskIDs[1])
+
+        store.uncomplete(taskID: taskIDs[1])
+
+        let tasks = store.fetchTasks()
+        let activeTasks = tasks.filter { !$0.isCompleted }
+        #expect(activeTasks.count == 3)
+        #expect(activeTasks.contains { $0.id == taskIDs[1] })
+    }
+}

diff --git a/Tests/Unit/TaskStoreOrderingTests.swift b/Tests/Unit/TaskStoreOrderingTests.swift

@@ -0,0 +1,186 @@
+import Foundation
+import Testing
+
+@testable import Listless_iOS
+
+@Suite("TaskStore Task Reordering", .serialized)
+@MainActor
+struct TaskStoreOrderingTests {
+
+    // MARK: - Initial State Tests
+
+    @Test("Initial sortOrder has 1000-point gaps")
+    func initialSortOrderHasThousandPointGaps() async throws {
+        let store = makeTestStore()
+
+        let task1 = store.createTask(title: "Task 1")
+        let task2 = store.createTask(title: "Task 2")
+        let task3 = store.createTask(title: "Task 3")
+
+        let tasks = store.fetchTasks()
+
+        // All tasks are active, so they should be the first 3
+        #expect(tasks.count == 3)
+
+        // Verify tasks are in ascending order
+        #expect(tasks[0].sortOrder < tasks[1].sortOrder)
+        #expect(tasks[1].sortOrder < tasks[2].sortOrder)
+
+        // Verify 1000-point gaps between tasks
+        #expect(tasks[1].sortOrder - tasks[0].sortOrder == 1000)
+        #expect(tasks[2].sortOrder - tasks[1].sortOrder == 1000)
+    }
+
+    // MARK: - Move Tests (Parameterized)
+
+    @Test("Move task to different positions", arguments: [
+        (from: 0, to: 2),
+        (from: 2, to: 0),
+        (from: 0, to: 1),
+        (from: 1, to: 0),
+        (from: 1, to: 2),
+        (from: 2, to: 1),
+    ])
+    func moveTaskToDifferentPositions(from: Int, to: Int) async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+        let taskToMove = taskIDs[from]
+
+        store.moveTask(taskID: taskToMove, toIndex: to)
+
+        let tasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(tasks[to].id == taskToMove)
+    }
+
+    // MARK: - Order Preservation Tests
+
+    @Test("Moving maintains 1000-point gaps")
+    func movingMaintainsThousandPointGaps() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 4)
+
+        store.moveTask(taskID: taskIDs[0], toIndex: 2)
+
+        let tasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(tasks[0].sortOrder == 0)
+        #expect(tasks[1].sortOrder == 1000)
+        #expect(tasks[2].sortOrder == 2000)
+        #expect(tasks[3].sortOrder == 3000)
+    }
+
+    @Test("Move task to same index does nothing")
+    func moveTaskToSameIndexDoesNothing() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+        let originalTasks = store.fetchTasks().filter { !$0.isCompleted }
+
+        store.moveTask(taskID: taskIDs[1], toIndex: 1)
+
+        let tasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(tasks[0].id == originalTasks[0].id)
+        #expect(tasks[1].id == originalTasks[1].id)
+        #expect(tasks[2].id == originalTasks[2].id)
+    }
+
+    // MARK: - Invalid Input Tests
+
+    @Test("Move with invalid ID does nothing")
+    func moveWithInvalidIDDoesNothing() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+        let originalTasks = store.fetchTasks().filter { !$0.isCompleted }
+        let invalidID = UUID()
+
+        store.moveTask(taskID: invalidID, toIndex: 0)
+
+        let tasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(tasks[0].id == originalTasks[0].id)
+        #expect(tasks[1].id == originalTasks[1].id)
+        #expect(tasks[2].id == originalTasks[2].id)
+    }
+
+    @Test("Move to negative index clamps to 0")
+    func moveToNegativeIndexClampsToZero() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+
+        store.moveTask(taskID: taskIDs[2], toIndex: -5)
+
+        let tasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(tasks[0].id == taskIDs[2])
+    }
+
+    @Test("Move to out-of-bounds index clamps to end")
+    func moveToOutOfBoundsIndexClampsToEnd() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+
+        store.moveTask(taskID: taskIDs[0], toIndex: 999)
+
+        let tasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(tasks[2].id == taskIDs[0])
+    }
+
+    // MARK: - Completed Task Tests
+
+    @Test("Moving only affects active tasks")
+    func movingOnlyAffectsActiveTasks() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 4)
+        store.complete(taskID: taskIDs[3])
+
+        store.moveTask(taskID: taskIDs[0], toIndex: 2)
+
+        let allTasks = store.fetchTasks()
+        let activeTasks = allTasks.filter { !$0.isCompleted }
+        let completedTasks = allTasks.filter { $0.isCompleted }
+
+        #expect(activeTasks.count == 3)
+        #expect(completedTasks.count == 1)
+        #expect(completedTasks[0].id == taskIDs[3])
+    }
+
+    @Test("Moving completed task does nothing")
+    func movingCompletedTaskDoesNothing() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+        store.complete(taskID: taskIDs[0])
+        let originalTasks = store.fetchTasks()
+
+        store.moveTask(taskID: taskIDs[0], toIndex: 1)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks[0].id == originalTasks[0].id)
+        #expect(tasks[1].id == originalTasks[1].id)
+        #expect(tasks[2].id == originalTasks[2].id)
+    }
+
+    // MARK: - Edge Cases
+
+    @Test("Move single task does nothing")
+    func moveSingleTaskDoesNothing() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Only task")
+
+        store.moveTask(taskID: task.id, toIndex: 0)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.count == 1)
+        #expect(tasks[0].id == task.id)
+    }
+
+    @Test("Move in empty store does nothing")
+    func moveInEmptyStoreDoesNothing() async throws {
+        let store = makeTestStore()
+        let randomID = UUID()
+
+        store.moveTask(taskID: randomID, toIndex: 0)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.isEmpty)
+    }
+
+    @Test("Multiple moves maintain order")
+    func multipleMoveMaintainOrder() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 4)
+
+        store.moveTask(taskID: taskIDs[0], toIndex: 3)
+        store.moveTask(taskID: taskIDs[2], toIndex: 0)
+
+        let tasks = store.fetchTasks().filter { !$0.isCompleted }
+        #expect(tasks[0].id == taskIDs[2])
+        #expect(tasks[3].id == taskIDs[0])
+    }
+}

diff --git a/Tests/Unit/TaskStoreTests.swift b/Tests/Unit/TaskStoreTests.swift

@@ -0,0 +1,189 @@
+import Foundation
+import Testing
+
+@testable import Listless_iOS
+
+@Suite("TaskStore CRUD Operations", .serialized)
+@MainActor
+struct TaskStoreTests {
+
+    // MARK: - Creation Tests
+
+    @Test("Create task with empty title")
+    func createTaskWithEmptyTitle() async throws {
+        let store = makeTestStore()
+
+        let task = store.createTask()
+
+        #expect(task.title == "")
+        #expect(task.id != UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
+        #expect(task.isCompleted == false)
+        #expect(task.createdAt.timeIntervalSinceNow > -1.0)
+    }
+
+    @Test("Create task with title")
+    func createTaskWithTitle() async throws {
+        let store = makeTestStore()
+
+        let task = store.createTask(title: "Buy groceries")
+
+        #expect(task.title == "Buy groceries")
+        #expect(task.id != UUID(uuidString: "00000000-0000-0000-0000-000000000000")!)
+    }
+
+    @Test("Create multiple tasks with unique IDs")
+    func createMultipleTasksWithUniqueIDs() async throws {
+        let store = makeTestStore()
+
+        let task1 = store.createTask(title: "Task 1")
+        let task2 = store.createTask(title: "Task 2")
+        let task3 = store.createTask(title: "Task 3")
+
+        #expect(task1.id != task2.id)
+        #expect(task2.id != task3.id)
+        #expect(task1.id != task3.id)
+    }
+
+    @Test("Created task has timestamps")
+    func createdTaskHasTimestamps() async throws {
+        let store = makeTestStore()
+
+        let beforeCreate = Date()
+        let task = store.createTask(title: "Test")
+        let afterCreate = Date()
+
+        #expect(task.createdAt >= beforeCreate)
+        #expect(task.createdAt <= afterCreate)
+        #expect(task.updatedAt >= beforeCreate)
+        #expect(task.updatedAt <= afterCreate)
+    }
+
+    // MARK: - Fetch Tests
+
+    @Test("Fetch tasks from empty store")
+    func fetchTasksFromEmptyStore() async throws {
+        let store = makeTestStore()
+
+        let tasks = store.fetchTasks()
+
+        #expect(tasks.isEmpty)
+    }
+
+    @Test("Fetch tasks returns created tasks")
+    func fetchTasksReturnsCreatedTasks() async throws {
+        let store = makeTestStore()
+        _ = store.createTask(title: "Task 1")
+        _ = store.createTask(title: "Task 2")
+
+        let tasks = store.fetchTasks()
+
+        #expect(tasks.count == 2)
+        #expect(tasks[0].title == "Task 1")
+        #expect(tasks[1].title == "Task 2")
+    }
+
+    // MARK: - Update Tests
+
+    @Test("Update task title")
+    func updateTaskTitle() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Original")
+
+        store.update(taskID: task.id, title: "Updated")
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.title == "Updated")
+    }
+
+    @Test("Update task title without saving")
+    func updateTaskTitleWithoutSaving() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Original")
+
+        store.updateWithoutSaving(taskID: task.id, title: "Updated")
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.first?.title == "Updated")
+    }
+
+    @Test("Update with invalid ID does nothing")
+    func updateWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        _ = store.createTask(title: "Task 1")
+        let invalidID = UUID()
+
+        store.update(taskID: invalidID, title: "Should not exist")
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.count == 1)
+        #expect(tasks.first?.title == "Task 1")
+    }
+
+    // MARK: - Delete Tests
+
+    @Test("Delete task")
+    func deleteTask() async throws {
+        let store = makeTestStore()
+        let task1 = store.createTask(title: "Task 1")
+        let task2 = store.createTask(title: "Task 2")
+
+        store.delete(taskID: task1.id)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.count == 1)
+        #expect(tasks.first?.id == task2.id)
+    }
+
+    @Test("Delete all tasks")
+    func deleteAllTasks() async throws {
+        let store = makeTestStore()
+        let task1 = store.createTask(title: "Task 1")
+        let task2 = store.createTask(title: "Task 2")
+
+        store.delete(taskID: task1.id)
+        store.delete(taskID: task2.id)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.isEmpty)
+    }
+
+    @Test("Delete with invalid ID does nothing")
+    func deleteWithInvalidIDDoesNothing() async throws {
+        let store = makeTestStore()
+        _ = store.createTask(title: "Task 1")
+        let invalidID = UUID()
+
+        store.delete(taskID: invalidID)
+
+        let tasks = store.fetchTasks()
+        #expect(tasks.count == 1)
+    }
+
+    // MARK: - Edge Cases
+
+    @Test("Task IDs persist across fetches")
+    func taskIDsPersistAcrossFetches() async throws {
+        let store = makeTestStore()
+        let task = store.createTask(title: "Test")
+        let originalID = task.id
+
+        let fetchedTasks = store.fetchTasks()
+        let fetchedID = fetchedTasks.first?.id
+
+        #expect(fetchedID == originalID)
+    }
+
+    @Test("Create task increments sortOrder")
+    func createTaskIncrementsSortOrder() async throws {
+        let store = makeTestStore()
+
+        let task1 = store.createTask(title: "Task 1")
+        let task2 = store.createTask(title: "Task 2")
+        let task3 = store.createTask(title: "Task 3")
+
+        #expect(task2.sortOrder > task1.sortOrder)
+        #expect(task3.sortOrder > task2.sortOrder)
+        #expect(task2.sortOrder - task1.sortOrder == 1000)
+        #expect(task3.sortOrder - task2.sortOrder == 1000)
+    }
+}