From c1c75f773c40ca00fb1f85d5ee40da760dcfdaf5 Mon Sep 17 00:00:00 2001 From: Alexander Zakharov <> Date: Sat, 7 Oct 2023 12:01:31 +0600 Subject: [PATCH] Add Tests --- VocabVerseTests/CategoryTests.swift | 154 +++++++++++++++++ VocabVerseTests/CorrectWritingTests.swift | 141 ++++++++++++++++ VocabVerseTests/LearningListTests.swift | 50 ++++++ VocabVerseTests/LearningTests.swift | 58 +++++++ VocabVerseTests/RootTests.swift | 197 ++++++++++++++++++++++ VocabVerseTests/VocabVerseTests.swift | 35 ++++ VocabVerseTests/WordFormTests.swift | 46 +++++ VocabVerseTests/WordListTests.swift | 104 ++++++++++++ 8 files changed, 785 insertions(+) create mode 100644 VocabVerseTests/CategoryTests.swift create mode 100644 VocabVerseTests/CorrectWritingTests.swift create mode 100644 VocabVerseTests/LearningListTests.swift create mode 100644 VocabVerseTests/LearningTests.swift create mode 100644 VocabVerseTests/RootTests.swift create mode 100644 VocabVerseTests/VocabVerseTests.swift create mode 100644 VocabVerseTests/WordFormTests.swift create mode 100644 VocabVerseTests/WordListTests.swift diff --git a/VocabVerseTests/CategoryTests.swift b/VocabVerseTests/CategoryTests.swift new file mode 100644 index 0000000..b2c08bf --- /dev/null +++ b/VocabVerseTests/CategoryTests.swift @@ -0,0 +1,154 @@ +import XCTest +import ComposableArchitecture +@testable import VocabVerse + +@MainActor +final class CategoryTests: XCTestCase { + func testOnAppearCategories() async { + let categories: [VocabVerse.Category] = [ + Category(id: UUID(0), name: "Test category", addedDate: .distantPast) + ] + + let store = TestStore(initialState: CategoryListFeature.State()) { + CategoryListFeature() + } withDependencies: { + $0.categoryClient.fetchCategories = { + categories + } + } + + await store.send(.onAppear) + await store.receive(.categoriesFetched(categories)) { + $0.categories = categories + $0.isCategoriesFetched = true + } + } + + func testDeleteCategory() async { + let store = TestStore(initialState: CategoryListFeature.State(categories: [ + Category(id: UUID(0), name: "Test category 1", addedDate: .distantPast), + Category(id: UUID(1), name: "Test category 2", addedDate: .distantPast) + ])) { + CategoryListFeature() + } withDependencies: { + $0.categoryClient = .testValue + } + + await store.send( + .deleteWordsCategoryTapped( + Category( + id: UUID(1), + name: "Test category 2", + addedDate: .distantPast + ) + ) + ) + await store.receive(.categoryDeleted(Category(id: UUID(1), name: "Test category 2", addedDate: .distantPast))) { + $0.categories = [Category(id: UUID(0), name: "Test category 1", addedDate: .distantPast)] + } + } + + func testAddCategory() async { + let store = TestStore(initialState: CategoryListFeature.State()) { + CategoryListFeature() + } withDependencies: { + $0.uuid = .incrementing + $0.date = .constant(.distantPast) + $0.categoryClient = .testValue + } + + var category = Category(id: UUID(0), addedDate: .distantPast) + + await store.send(.addCategoryButtonTapped) { + $0.addCategory = CategoryFormFeature.State(category: category) + } + + category.name = "Test category" + await store.send(.addCategory(.presented(.set(\.$category, category)))) { + $0.addCategory?.category.name = "Test category" + } + + await store.send(.createCategoryButtonTapped) + await store.receive(.categoryCreated(category)) { + $0.addCategory = nil + $0.categories = [Category(id: UUID(0), name: "Test category", addedDate: .distantPast)] + } + } + + func testAddWordToCategory() async { + var word = Word(id: UUID(0), categoryId: UUID(0), addedDate: .distantPast) + let category = Category(id: UUID(0), addedDate: .distantPast) + let store = TestStore(initialState: CategoryListFeature.State(categories: [category])) { + CategoryListFeature() + } withDependencies: { + $0.date = .constant(.distantPast) + $0.uuid = .incrementing + $0.categoryClient = .testValue + } + + await store.send(.path(.push(id: 0, state: .wordsList(WordsListFeature.State(categoryId: UUID(0)))))) { + $0.path[id: 0] = .wordsList(WordsListFeature.State(categoryId: UUID(0))) + } + + await store.send(.path(.element(id: 0, action: .wordsList(.addButtonTapped)))) { + $0.path[id: 0] = .wordsList(WordsListFeature.State( + addWord: WordFormFeature.State(word: word, formType: .add), + categoryId: UUID(0) + )) + } + word.nativeWord = "Native word" + word.translation = "Translation" + + await store.send(.path(.element(id: 0, action: .wordsList(.addWord(.presented(.set(\.$word, word))))))) { + $0.path[id: 0] = .wordsList( + WordsListFeature.State( + addWord: WordFormFeature.State( + word: Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ), + formType: .add + ), + categoryId: UUID(0) + ) + ) + } + + await store.send(.path(.element(id: 0, action: .wordsList(.saveWordButtonTaped)))) + await store.receive(.path(.element(id: 0, action: .wordsList(.wordSaved(word))))) { + $0.path[id: 0] = .wordsList( + WordsListFeature.State( + addWord: nil, + words: [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ], + categoryId: UUID(0) + ) + ) + $0.categories = [ + Category( + id: UUID(0), + words: [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ], + addedDate: .distantPast + ) + ] + } + } +} diff --git a/VocabVerseTests/CorrectWritingTests.swift b/VocabVerseTests/CorrectWritingTests.swift new file mode 100644 index 0000000..2fe60bf --- /dev/null +++ b/VocabVerseTests/CorrectWritingTests.swift @@ -0,0 +1,141 @@ +import XCTest +import ComposableArchitecture +@testable import VocabVerse + +@MainActor +final class CorrectWritingTests: XCTestCase { + func testHint() async { + let word: Word = Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Te", + addedDate: .distantPast + ) + + let store = TestStore(initialState: CorrectWritingFeature.State(words: [word])) { + CorrectWritingFeature() + } + + await store.send(.hintTapped) { + $0.userInput = "T" + $0.currentWordState.hintProgress = 1 + } + await store.send(.hintTapped) { + $0.userInput = "Te" + $0.currentWordState.hintProgress = 2 + } + await store.send(.hintTapped) + } + + func testCorrectTranlation() async { + let word: Word = Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + + let store = TestStore( + initialState: CorrectWritingFeature.State( + words: [word], + userInput: "Translation" + ) + ) { + CorrectWritingFeature() + } + + await store.send(.checkTranslation) { + $0.currentWordState.isWordCorrect = true + $0.currentWordState.attempsCount = 1 + $0.correctWordsCount = 1 + } + } + + func testNextWordSwitching() async { + let word: Word = Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + + let store = TestStore( + initialState: CorrectWritingFeature.State( + words: [word], + userInput: "Translation" + ) + ) { + CorrectWritingFeature() + } + + store.exhaustivity = .off + + await store.send(.checkTranslation) + + await store.send(.nextWordButtonTapped) { + $0.userInput = "" + $0.correctWordsCount = 1 + $0.currentWordIndex = 1 + $0.currentWordState.attempsCount = 0 + $0.currentWordState.isWordCorrect = false + $0.currentWordState.hintProgress = 0 + } + } + + func testTotalAttempsReached() async { + let word: Word = Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + + let store = TestStore( + initialState: CorrectWritingFeature.State( + words: [word], + userInput: "Incorrect word" + ) + ) { + CorrectWritingFeature() + } + + store.exhaustivity = .off + + await store.send(.checkTranslation) + await store.send(.checkTranslation) + await store.send(.checkTranslation) { + $0.currentWordState.attempsCount = 3 + $0.currentWordState.isWordCorrect = false + $0.incorrectWordsCount = 1 + } + } + + func testReset() async { + let word: Word = Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + + let store = TestStore( + initialState: CorrectWritingFeature.State( + words: [word], + currentWordIndex: 1, + userInput: "Some word", + currentWordState: .init(attempsCount: 2, isWordCorrect: true) + ) + ) { + CorrectWritingFeature() + } + + await store.send(.reset) { + $0 = CorrectWritingFeature.State(words: [word]) + } + } +} diff --git a/VocabVerseTests/LearningListTests.swift b/VocabVerseTests/LearningListTests.swift new file mode 100644 index 0000000..2712b1f --- /dev/null +++ b/VocabVerseTests/LearningListTests.swift @@ -0,0 +1,50 @@ +import XCTest +import ComposableArchitecture +@testable import VocabVerse + +@MainActor +final class LearningListTests: XCTestCase { + func testOnAppearLearningList() async { + let categories: [VocabVerse.Category] = [ + Category(id: UUID(0), name: "Test category", addedDate: .distantPast) + ] + + let store = TestStore(initialState: LearningListFeature.State()) { + LearningListFeature() + } withDependencies: { + $0.categoryClient.fetchCategories = { categories } + $0.stateClient = .testValue + } + + await store.send(.onAppear) + await store.receive(.categoriesFetched(categories)) { + $0.categories = [Category(id: UUID(0), name: "Test category", addedDate: .distantPast)] + $0.isCategoriesFecthed = true + } + } + + func testSelectCategory() async { + let categories: [VocabVerse.Category] = [ + Category(id: UUID(0), name: "Test category1", addedDate: .distantPast), + Category(id: UUID(1), name: "Test category2", addedDate: .distantPast) + ] + + let store = TestStore(initialState: LearningListFeature.State(categories: categories)) { + LearningListFeature() + } + + await store.send(.selectCategoryButtonTapped) { + $0.categorySelect = CategorySelectFeature.State(categories: categories) + } + + await store.send(.categorySelect(.presented(.selectCategoryTapped(categories[0])))) { + $0.categorySelect?.selectedCategories = [categories[0].id] + } + + await store.send(.categoriesSelected([categories[0].id])) { + $0.categorySelect = nil + $0.categories = categories + $0.selectedCategories = [categories[0].id] + } + } +} diff --git a/VocabVerseTests/LearningTests.swift b/VocabVerseTests/LearningTests.swift new file mode 100644 index 0000000..8183764 --- /dev/null +++ b/VocabVerseTests/LearningTests.swift @@ -0,0 +1,58 @@ +import XCTest +import ComposableArchitecture +@testable import VocabVerse + +@MainActor +final class LearningTests: XCTestCase { + func testCardFlipped() async { + let words: [Word] = [ + Word(id: UUID(0), categoryId: UUID(0), nativeWord: "Native", translation: "Translation", addedDate: .distantPast) + ] + + let store = TestStore(initialState: FlashCardsFeature.State(words: words)) { + FlashCardsFeature() + } + + await store.send(.cardTapped) { + $0.isCurrentCardFlipped = true + } + + } + + func testWordsLearningProccess() async { + let words: [Word] = [ + Word(id: UUID(0), categoryId: UUID(0), nativeWord: "Native", translation: "Translation", addedDate: .distantPast), + Word(id: UUID(1), categoryId: UUID(0), nativeWord: "Native", translation: "Translation", addedDate: .distantPast), + Word(id: UUID(2), categoryId: UUID(0), nativeWord: "Native", translation: "Translation", addedDate: .distantPast) + ] + + let store = TestStore(initialState: FlashCardsFeature.State(words: words)) { + FlashCardsFeature() + } withDependencies: { + $0.categoryClient = .testValue + } + + await store.send(.correctWordTapped) { + $0.correctWordsScore = 1 + $0.currentIndex = 1 + } + + await store.send(.incorrectWordTapped) { + $0.incorrectWordScore = 1 + $0.correctWordsScore = 1 + $0.currentIndex = 2 + } + + await store.send(.correctWordTapped) { + $0.correctWordsScore = 2 + $0.incorrectWordScore = 1 + $0.currentIndex = 3 + } + + await store.send(.reset) { + $0.correctWordsScore = 0 + $0.incorrectWordScore = 0 + $0.currentIndex = 0 + } + } +} diff --git a/VocabVerseTests/RootTests.swift b/VocabVerseTests/RootTests.swift new file mode 100644 index 0000000..230cc57 --- /dev/null +++ b/VocabVerseTests/RootTests.swift @@ -0,0 +1,197 @@ +import XCTest +import ComposableArchitecture +@testable import VocabVerse + +@MainActor +final class AppTests: XCTestCase { + func testSelectedLearningTab() async { + let store = TestStore(initialState: AppFeature.State()) { + AppFeature() + } + + await store.send(.tabChanged(1)) { + $0.selectedTab = .categoryList + } + + await store.send(.tabChanged(0)) { + $0.selectedTab = .learningList + } + } + + func testAddCategoryHandled() async { + let category: VocabVerse.Category = Category( + id: UUID(0), + words: [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ], + addedDate: .distantPast + ) + + let store = TestStore(initialState: AppFeature.State()) { + AppFeature() + } withDependencies: { + $0.uuid = .incrementing + $0.date = .constant(.distantPast) + } + + await store.send(.categoryList(.categoryCreated(category))) { + $0.learningListState.categories = [category] + $0.categoryListState.categories = [category] + } + } + + func testDeleteCategoryHandled() async { + let category: VocabVerse.Category = Category( + id: UUID(0), + words: [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ], + addedDate: .distantPast + ) + + let categoryListState = CategoryListFeature.State(categories: [category]) + let learningListState = LearningListFeature.State(categories: [category]) + + let store = TestStore(initialState: AppFeature.State( + categoryListState: categoryListState, + learningListState: learningListState + )) { + AppFeature() + } + + await store.send(.categoryList(.categoryDeleted(category))) { + $0.learningListState.categories = [] + $0.categoryListState.categories = [] + } + } + + func testAddWordInCategoryHandled() async { + var category: VocabVerse.Category = Category( + id: UUID(0), + words: [], + addedDate: .distantPast + ) + + let categoryListState = CategoryListFeature.State(categories: [category]) + let learningListState = LearningListFeature.State(categories: [category]) + + let store = TestStore(initialState: AppFeature.State( + categoryListState: categoryListState, + learningListState: learningListState + )) { + AppFeature() + } + + store.exhaustivity = .off + + await store.send(.categoryList(.path(.push(id: 0, state: .wordsList(.init(words: category.words, categoryId: UUID(0))))))) + + let word = Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + category.words = [word] + + await store.send(.categoryList(.path(.element(id: 0, action: .wordsList(.wordSaved(word)))))) { + $0.categoryListState.categories = [category] + $0.learningListState.categories = [category] + } + } + + func testEditWordInCategoryHandled() async { + var category: VocabVerse.Category = Category( + id: UUID(0), + words: [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ], + addedDate: .distantPast + ) + + let categoryListState = CategoryListFeature.State(categories: [category]) + let learningListState = LearningListFeature.State(categories: [category]) + + let store = TestStore(initialState: AppFeature.State( + categoryListState: categoryListState, + learningListState: learningListState + )) { + AppFeature() + } + + store.exhaustivity = .off + + await store.send(.categoryList(.path(.push(id: 0, state: .wordsList(.init(words: category.words, categoryId: UUID(0))))))) + + let editedWord = Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Edited Native word", + translation: "Edited Translation", + addedDate: .distantPast + ) + category.words = [editedWord] + + await store.send(.categoryList(.path(.element(id: 0, action: .wordsList(.wordEdited(editedWord)))))) { + $0.categoryListState.categories = [category] + $0.learningListState.categories = [category] + } + } + + func testDeleteWordInCategoryHandled() async { + var category: VocabVerse.Category = Category( + id: UUID(0), + words: [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ], + addedDate: .distantPast + ) + + let categoryListState = CategoryListFeature.State(categories: [category]) + let learningListState = LearningListFeature.State(categories: [category]) + + let store = TestStore(initialState: AppFeature.State( + categoryListState: categoryListState, + learningListState: learningListState + )) { + AppFeature() + } + + store.exhaustivity = .off + + await store.send(.categoryList(.path(.push(id: 0, state: .wordsList(.init(words: category.words, categoryId: UUID(0))))))) + + let deletedWord = category.words[0] + category.words = [] + + await store.send(.categoryList(.path(.element(id: 0, action: .wordsList(.wordDeleted(deletedWord)))))) { + $0.categoryListState.categories = [category] + $0.learningListState.categories = [category] + } + } +} diff --git a/VocabVerseTests/VocabVerseTests.swift b/VocabVerseTests/VocabVerseTests.swift new file mode 100644 index 0000000..ed47f60 --- /dev/null +++ b/VocabVerseTests/VocabVerseTests.swift @@ -0,0 +1,35 @@ +// +// VocabVerseTests.swift +// VocabVerseTests +// +// Created by apzakharov on 21.08.2023. +// + +import XCTest + +final class VocabVerseTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/VocabVerseTests/WordFormTests.swift b/VocabVerseTests/WordFormTests.swift new file mode 100644 index 0000000..44b7979 --- /dev/null +++ b/VocabVerseTests/WordFormTests.swift @@ -0,0 +1,46 @@ +import XCTest +import ComposableArchitecture +@testable import VocabVerse + +@MainActor +final class WordFormTests: XCTestCase { + func testIsValidForm() async { + let store = TestStore( + initialState: WordFormFeature.State( + word: Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Test", + translation: "Translation", + addedDate: .distantPast + ), + formType: .add + ) + ) { + WordFormFeature() + } + + await store.send(.validateForm) { + $0.isValidForm = true + } + } + + func testUnvalidForm() async { + let store = TestStore( + initialState: WordFormFeature.State( + word: Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "", + translation: "", + addedDate: .distantPast + ), + formType: .add + ) + ) { + WordFormFeature() + } + + await store.send(.validateForm) + } +} diff --git a/VocabVerseTests/WordListTests.swift b/VocabVerseTests/WordListTests.swift new file mode 100644 index 0000000..6ba0bc3 --- /dev/null +++ b/VocabVerseTests/WordListTests.swift @@ -0,0 +1,104 @@ +import XCTest +import ComposableArchitecture +@testable import VocabVerse + +@MainActor +final class WordListTests: XCTestCase { + func testDeleteWord() async { + let words: [Word] = [ + Word(id: UUID(0), categoryId: UUID(0), nativeWord: "Word", translation: "Translation", addedDate: .distantPast), + Word(id: UUID(1), categoryId: UUID(0), nativeWord: "Word1", translation: "Translation1", addedDate: .distantPast) + ] + let store = TestStore(initialState: WordsListFeature.State(words: words, categoryId: UUID(0))) { + WordsListFeature() + } withDependencies: { + $0.categoryClient = .testValue + } + + let deletedWords: [Word] = [ + Word(id: UUID(1), categoryId: UUID(0), nativeWord: "Word1", translation: "Translation1", addedDate: .distantPast) + ] + + await store.send(.deleteWordButtonTapped(words.first!)) + await store.receive(.wordDeleted(words.first!)) { + $0.words = deletedWords + } + } + + func testAddWord() async { + let store = TestStore(initialState: WordsListFeature.State(categoryId: UUID(0))) { + WordsListFeature() + } withDependencies: { + $0.uuid = .incrementing + $0.date = .constant(.distantPast) + $0.categoryClient = .testValue + } + + var word = Word(id: UUID(0), categoryId: UUID(0), addedDate: .distantPast) + + await store.send(.addButtonTapped) { + $0.addWord = WordFormFeature.State(word: word, formType: .add) + } + word.nativeWord = "Native word" + word.translation = "Translation" + await store.send(.addWord(.presented(.set(\.$word, word)))) { + $0.addWord?.word.nativeWord = "Native word" + $0.addWord?.word.translation = "Translation" + } + + await store.send(.saveWordButtonTaped) + await store.receive(.wordSaved(word)) { + $0.addWord = nil + $0.words = [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ] + } + } + + func testEditWord() async { + var words: [Word] = [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "Native word", + translation: "Translation", + addedDate: .distantPast + ) + ] + let store = TestStore(initialState: WordsListFeature.State(words: words, categoryId: UUID(0))) { + WordsListFeature() + } withDependencies: { + $0.categoryClient = .testValue + } + + + await store.send(.wordTapped(words[0])) { + $0.addWord = WordFormFeature.State(word: words[0], formType: .edit) + $0.addWord?.isValidForm = true + } + await store.send(.addWord(.presented(.set(\.$word, words[0])))) + + words[0].nativeWord = "New word" + words[0].translation = "New Translation" + + await store.send(.editWordButtonTapped(words[0])) + await store.receive(.wordEdited(words[0])) { + $0.addWord = nil + $0.words = [ + Word( + id: UUID(0), + categoryId: UUID(0), + nativeWord: "New word", + translation: "New Translation", + addedDate: .distantPast + ) + ] + } + } +}