diff --git a/MindDump/MindDumpApp.swift b/MindDump/MindDumpApp.swift index d5f8de6..314a7bb 100644 --- a/MindDump/MindDumpApp.swift +++ b/MindDump/MindDumpApp.swift @@ -7,7 +7,11 @@ struct MindDumpApp: App { @State private var store: ListStore init() { - let container = try! ModelContainer(for: TodoList.self, TodoItem.self) + let isUITest = ProcessInfo.processInfo.arguments.contains("UI_TESTING") + let config = isUITest + ? ModelConfiguration(isStoredInMemoryOnly: true) + : ModelConfiguration() + let container = try! ModelContainer(for: TodoList.self, TodoItem.self, configurations: config) self.container = container self._store = State(initialValue: ListStore(modelContext: container.mainContext)) } diff --git a/MindDumpTests/MindDumpTests.swift b/MindDumpTests/MindDumpTests.swift new file mode 100644 index 0000000..7c31094 --- /dev/null +++ b/MindDumpTests/MindDumpTests.swift @@ -0,0 +1,139 @@ +import XCTest +import SwiftData +@testable import MindDump + +final class TodoItemTests: XCTestCase { + + // MARK: - isOverdue + + /// Past deadline should be overdue + func testIsOverdue_deadlineYesterday_returnsTrue() { + let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! + let item = TodoItem(title: "Test", deadline: yesterday) + XCTAssertTrue(item.isOverdue) + } + + /// Completed items are never overdue, even with past deadline + func testIsOverdue_completedWithPastDeadline_returnsFalse() { + let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! + let item = TodoItem(title: "Test", isCompleted: true, deadline: yesterday) + XCTAssertFalse(item.isOverdue) + } + + +} + +final class TodoListTests: XCTestCase { + + // MARK: - openItems / completedItems + + /// Items are correctly split into open and completed + func testOpenAndCompletedItems_correctSplit() { + let open1 = TodoItem(title: "Open 1") + let open2 = TodoItem(title: "Open 2") + let done = TodoItem(title: "Done", isCompleted: true) + + let list = TodoList(name: "Test", items: [open1, done, open2]) + + XCTAssertEqual(list.openItems.count, 2) + XCTAssertEqual(list.completedItems.count, 1) + XCTAssertEqual(list.completedItems.first?.title, "Done") + } + + /// Open items are sorted oldest first + func testOpenItems_sortedByCreatedAtAscending() { + let older = TodoItem(title: "Older", createdAt: Date().addingTimeInterval(-100)) + let newer = TodoItem(title: "Newer", createdAt: Date()) + + let list = TodoList(name: "Test", items: [newer, older]) + + XCTAssertEqual(list.openItems.map(\.title), ["Older", "Newer"]) + } + + /// Completed items are sorted newest first (by modifiedAt) + func testCompletedItems_sortedByModifiedAtDescending() { + let earlier = TodoItem(title: "Earlier", isCompleted: true, modifiedAt: Date().addingTimeInterval(-100)) + let later = TodoItem(title: "Later", isCompleted: true, modifiedAt: Date()) + + let list = TodoList(name: "Test", items: [earlier, later]) + + XCTAssertEqual(list.completedItems.map(\.title), ["Later", "Earlier"]) + } + +} + +// MARK: - ListStore Tests + +@MainActor +final class ListStoreTests: XCTestCase { + + private var container: ModelContainer! + private var store: ListStore! + + override func setUp() { + let config = ModelConfiguration(isStoredInMemoryOnly: true) + container = try! ModelContainer(for: TodoList.self, TodoItem.self, configurations: config) + store = ListStore(modelContext: container.mainContext) + } + + // MARK: - addItem / deleteItem + + /// Adding an item increases the list count and stores the title + func testAddItem_increasesCount() { + let inboxID = store.inboxID + store.addItem(to: inboxID, title: "Buy milk") + + XCTAssertEqual(store.lists.first!.items.count, 1) + XCTAssertEqual(store.lists.first!.items.first?.title, "Buy milk") + } + + /// Deleting an item removes it from the list + func testDeleteItem_removesItem() { + let inboxID = store.inboxID + store.addItem(to: inboxID, title: "Temp") + let itemID = store.lists.first!.items.first!.id + + store.deleteItem(itemID, from: inboxID) + + XCTAssertTrue(store.lists.first!.items.isEmpty) + } + + // MARK: - toggleItemCompleted + + /// Toggling flips isCompleted back and forth + func testToggleItemCompleted_flipsStatus() { + let inboxID = store.inboxID + store.addItem(to: inboxID, title: "Task") + let itemID = store.lists.first!.items.first!.id + + XCTAssertFalse(store.lists.first!.items.first!.isCompleted) + + store.toggleItemCompleted(itemID, in: inboxID) + XCTAssertTrue(store.lists.first!.items.first!.isCompleted) + + store.toggleItemCompleted(itemID, in: inboxID) + XCTAssertFalse(store.lists.first!.items.first!.isCompleted) + } + + // MARK: - moveItem + + /// Moving an item removes it from the source list, adds it to the target list, and updates modifiedAt + func testMoveItem_movesAcrossLists() { + let inboxID = store.inboxID + store.addList(name: "Arbeit") + let arbeitID = store.lists.first { $0.name == "Arbeit" }!.id + + store.addItem(to: inboxID, title: "Wichtige Aufgabe") + let itemID = store.lists.first { $0.isInbox }!.items.first!.id + + store.moveItem(itemID, from: inboxID, to: arbeitID) + + let inbox = store.lists.first { $0.isInbox }! + let arbeit = store.lists.first { $0.name == "Arbeit" }! + + XCTAssertTrue(inbox.items.isEmpty, "Item should be removed from source lists") + XCTAssertEqual(arbeit.items.count, 1, "Item should be in target list") + XCTAssertEqual(arbeit.items.first?.title, "Wichtige Aufgabe") + XCTAssertNotNil(arbeit.items.first?.modifiedAt, "modifiedAt should be set after move") + } +} diff --git a/MindDumpUITests/MindDumpUITests.swift b/MindDumpUITests/MindDumpUITests.swift new file mode 100644 index 0000000..ba5c97b --- /dev/null +++ b/MindDumpUITests/MindDumpUITests.swift @@ -0,0 +1,52 @@ +import XCTest + +final class MindDumpUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + /// Tapping the MindDumpButton, entering a title and saving should show the entry in the list + @MainActor + func testQuickDump_createsEntry() throws { + let app = XCUIApplication() + app.launchArguments = ["UI_TESTING"] + app.launch() + + // Tap the MindDumpButton button + app.buttons["🧠"].tap() + + // Type a title in the editor sheet + let titleField = app.textFields["Titel"] + XCTAssertTrue(titleField.waitForExistence(timeout: 2)) + titleField.typeText("UI Test Eintrag") + + // Add a note + app.buttons["Notiz"].tap() + let noteField = app.textFields["Notiz hinzufügen..."] + XCTAssertTrue(noteField.waitForExistence(timeout: 2)) + noteField.tap() + noteField.typeText("Wichtige Notiz") + + // Add high priority — tap the add-field button, then the picker row, then the option + app.buttons["Priorität"].tap() + app.buttons["Priorität, 🟡 Mittel"].tap() + app.buttons["🔴 Hoch"].tap() + + // Tap "Fertig" to save + app.buttons["Fertig"].tap() + + // Navigate into Inbox to see the entry + app.staticTexts["Inbox"].tap() + + // Verify the entry appears in the list + let entryExists = app.descendants(matching: .any)["UI Test Eintrag"].waitForExistence(timeout: 3) + XCTAssertTrue(entryExists, "Entry title should be visible") + + // Verify the note snippet is shown below the title + XCTAssertTrue(app.descendants(matching: .any)["Wichtige Notiz"].exists, "Note should be visible") + + // Verify the red priority dot is shown + XCTAssertTrue(app.images["circle.fill"].exists, "Priority dot should be visible") + } +}