240 lines
6.4 KiB
Swift
240 lines
6.4 KiB
Swift
import Foundation
|
|||
|
|
import SwiftData
|
||
|
|
import Testing
|
||
|
|
|
||
|
|
@testable import Todos
|
||
|
|
|
||
|
|
@Suite("NewTaskView ViewModel Tests")
|
||
|
|
struct NewTaskViewModelTests {
|
||
|
|
|
||
|
|
// MARK: Initialization tests
|
||
|
|
|
||
|
|
@Test("View model initializes with default values")
|
||
|
|
func initialization() {
|
||
|
|
// GIVEN
|
||
|
|
// WHEN
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
#expect(viewModel.title == "")
|
||
|
|
#expect(viewModel.note == "")
|
||
|
|
#expect(viewModel.due.timeIntervalSinceNow < 1.0)
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: Properties tests
|
||
|
|
|
||
|
|
@Test("due property can be modified")
|
||
|
|
func dueModifications() {
|
||
|
|
// GIVEN
|
||
|
|
let dateInsert = Date(timeIntervalSince1970: 1000000000)
|
||
|
|
let dateUpdate = Date(timeIntervalSince1970: 2000000000)
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
viewModel.due = dateInsert
|
||
|
|
#expect(viewModel.due == dateInsert)
|
||
|
|
|
||
|
|
viewModel.due = dateUpdate
|
||
|
|
#expect(viewModel.due == dateUpdate)
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test("note property can be modified")
|
||
|
|
func noteModifications() {
|
||
|
|
// GIVEN
|
||
|
|
let noteInsert = "Initial note"
|
||
|
|
let noteUpdate = "Updated note"
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
viewModel.note = noteInsert
|
||
|
|
#expect(viewModel.note == noteInsert)
|
||
|
|
|
||
|
|
viewModel.note = noteUpdate
|
||
|
|
#expect(viewModel.note == noteUpdate)
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test("title property can be modified")
|
||
|
|
func titleModifications() {
|
||
|
|
// GIVEN
|
||
|
|
let titleInsert = "Initial title"
|
||
|
|
let titleUpdate = "Updated title"
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
viewModel.title = titleInsert
|
||
|
|
#expect(viewModel.title == titleInsert)
|
||
|
|
|
||
|
|
viewModel.title = titleUpdate
|
||
|
|
#expect(viewModel.title == titleUpdate)
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: Computed tests
|
||
|
|
|
||
|
|
@Test(
|
||
|
|
"canConfirm property validation",
|
||
|
|
arguments: zip(
|
||
|
|
["", "Some title goes here...", " "],
|
||
|
|
[false, true, false]
|
||
|
|
)
|
||
|
|
)
|
||
|
|
func canConfirm(
|
||
|
|
with title: String,
|
||
|
|
expects expectation: Bool
|
||
|
|
) {
|
||
|
|
// GIVEN
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
viewModel.title = title
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
#expect(viewModel.canConfirm == expectation)
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test("canConfirm updates when title changes")
|
||
|
|
func canConfirmWithTitleChanges() {
|
||
|
|
// GIVEN
|
||
|
|
let titleEmpty = ""
|
||
|
|
let titleFilled = "New task"
|
||
|
|
let titleTrimmed = " "
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
viewModel.title = titleEmpty
|
||
|
|
#expect(viewModel.canConfirm == false)
|
||
|
|
|
||
|
|
viewModel.title = titleFilled
|
||
|
|
#expect(viewModel.canConfirm == true)
|
||
|
|
|
||
|
|
viewModel.title = titleTrimmed
|
||
|
|
#expect(viewModel.canConfirm == false)
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: Functions tests
|
||
|
|
|
||
|
|
@Test("save creates todo with all properties")
|
||
|
|
@MainActor
|
||
|
|
func saveWithAllProperties() throws {
|
||
|
|
// GIVEN
|
||
|
|
let container = try ModelContainer(
|
||
|
|
for: Todo.self,
|
||
|
|
configurations: .init(isStoredInMemoryOnly: true)
|
||
|
|
)
|
||
|
|
let context = container.mainContext
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
viewModel.title = "Complete project"
|
||
|
|
viewModel.note = "Finish by end of week"
|
||
|
|
viewModel.due = Date(timeIntervalSince1970: 1000000000)
|
||
|
|
|
||
|
|
viewModel.save(to: context)
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
let descriptor = FetchDescriptor<Todo>()
|
||
|
|
let todos = try context.fetch(descriptor)
|
||
|
|
let todo = try #require(todos.first)
|
||
|
|
|
||
|
|
#expect(todo.title == viewModel.title)
|
||
|
|
#expect(todo.note == viewModel.note)
|
||
|
|
#expect(todo.due == viewModel.due)
|
||
|
|
#expect(todo.completed == nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test("save creates todo without note when note is empty")
|
||
|
|
@MainActor
|
||
|
|
func saveWithEmptyNote() throws {
|
||
|
|
// GIVEN
|
||
|
|
let container = try ModelContainer(
|
||
|
|
for: Todo.self,
|
||
|
|
configurations: .init(isStoredInMemoryOnly: true)
|
||
|
|
)
|
||
|
|
let context = container.mainContext
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
viewModel.title = "Task without note"
|
||
|
|
viewModel.note = ""
|
||
|
|
viewModel.due = Date()
|
||
|
|
|
||
|
|
viewModel.save(to: context)
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
let descriptor = FetchDescriptor<Todo>()
|
||
|
|
let todos = try context.fetch(descriptor)
|
||
|
|
let todo = try #require(todos.first)
|
||
|
|
|
||
|
|
#expect(todo.title == viewModel.title)
|
||
|
|
#expect(todo.note == nil)
|
||
|
|
#expect(todo.due == viewModel.due)
|
||
|
|
#expect(todo.completed == nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test("save creates todo with note containing only whitespace as nil")
|
||
|
|
@MainActor
|
||
|
|
func saveWithWhitespaceNote() throws {
|
||
|
|
// GIVEN
|
||
|
|
let container = try ModelContainer(
|
||
|
|
for: Todo.self,
|
||
|
|
configurations: .init(isStoredInMemoryOnly: true)
|
||
|
|
)
|
||
|
|
let context = container.mainContext
|
||
|
|
let viewModel = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
viewModel.title = "Task with whitespace note"
|
||
|
|
viewModel.note = " "
|
||
|
|
viewModel.due = Date()
|
||
|
|
|
||
|
|
viewModel.save(to: context)
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
let descriptor = FetchDescriptor<Todo>()
|
||
|
|
let todos = try context.fetch(descriptor)
|
||
|
|
let todo = try #require(todos.first)
|
||
|
|
|
||
|
|
#expect(todo.title == viewModel.title)
|
||
|
|
#expect(todo.note == nil)
|
||
|
|
#expect(todo.due == viewModel.due)
|
||
|
|
#expect(todo.completed == nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test("save inserts multiple todos independently")
|
||
|
|
@MainActor
|
||
|
|
func saveMultipleTodos() throws {
|
||
|
|
// GIVEN
|
||
|
|
let container = try ModelContainer(
|
||
|
|
for: Todo.self,
|
||
|
|
configurations: .init(isStoredInMemoryOnly: true)
|
||
|
|
)
|
||
|
|
let context = container.mainContext
|
||
|
|
let viewModelFirst = NewTaskView.ViewModel()
|
||
|
|
let viewModelSecond = NewTaskView.ViewModel()
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
viewModelFirst.title = "First task"
|
||
|
|
viewModelFirst.save(to: context)
|
||
|
|
|
||
|
|
viewModelSecond.title = "Second task"
|
||
|
|
viewModelSecond.note = "With a note"
|
||
|
|
viewModelSecond.save(to: context)
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
let descriptor = FetchDescriptor<Todo>()
|
||
|
|
let todos = try context.fetch(descriptor)
|
||
|
|
|
||
|
|
#expect(todos.count == 2)
|
||
|
|
#expect(todos.contains { $0.title == "First task" })
|
||
|
|
#expect(todos.contains { $0.title == "Second task" })
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|