Clean up repository structure and release docs
This commit is contained in:
290
tests/app/AppStateWorkflowTests.swift
Normal file
290
tests/app/AppStateWorkflowTests.swift
Normal file
@@ -0,0 +1,290 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
import IHatePDFsCore
|
||||
import PDFKit
|
||||
import XCTest
|
||||
@testable import IHatePDFs
|
||||
|
||||
@MainActor
|
||||
final class AppStateWorkflowTests: XCTestCase {
|
||||
func testOpeningDocumentStartsInFocusedReadingWorkflow() throws {
|
||||
let url = try makeTemporaryPDF()
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_280)
|
||||
appState.showLeftSidebar = true
|
||||
appState.leftSidebarMode = .annotations
|
||||
appState.showCommentsSidebar = true
|
||||
appState.sidebarMode = .highlights
|
||||
appState.commentSearchText = "draft"
|
||||
appState.commentFilter = .withComments
|
||||
appState.selectedKindFilter = .comment
|
||||
appState.selectedAuthorFilter = "Someone"
|
||||
appState.selectedStatusFilter = ReviewState.reviewed
|
||||
appState.collapsedPageIndexes = [0]
|
||||
|
||||
appState.loadDocument(from: url)
|
||||
|
||||
XCTAssertNotNil(appState.document)
|
||||
XCTAssertEqual(appState.documentURL, url)
|
||||
XCTAssertFalse(appState.showLeftSidebar)
|
||||
XCTAssertEqual(appState.leftSidebarMode, .pages)
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .annotations)
|
||||
XCTAssertEqual(appState.commentSearchText, "")
|
||||
XCTAssertEqual(appState.commentFilter, .all)
|
||||
XCTAssertNil(appState.selectedKindFilter)
|
||||
XCTAssertEqual(appState.selectedAuthorFilter, "All Authors")
|
||||
XCTAssertEqual(appState.selectedStatusFilter, ReviewState.allStatuses)
|
||||
XCTAssertTrue(appState.collapsedPageIndexes.isEmpty)
|
||||
XCTAssertFalse(appState.hasUnsavedChanges)
|
||||
}
|
||||
|
||||
func testOpeningDocumentCollapsesSidebars() throws {
|
||||
let url = try makeTemporaryPDF()
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_280)
|
||||
appState.showLeftSidebar = true
|
||||
appState.leftSidebarMode = .annotations
|
||||
appState.showCommentsSidebar = true
|
||||
appState.sidebarMode = .highlights
|
||||
|
||||
appState.loadDocument(from: url)
|
||||
|
||||
XCTAssertFalse(appState.showLeftSidebar)
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
}
|
||||
|
||||
func testDroppingPDFWhileDocumentOpenReplacesThroughAppState() async throws {
|
||||
let firstURL = try makeTemporaryPDF()
|
||||
let secondURL = try makeTemporaryPDF()
|
||||
defer {
|
||||
try? FileManager.default.removeItem(at: firstURL)
|
||||
try? FileManager.default.removeItem(at: secondURL)
|
||||
}
|
||||
|
||||
let appState = AppState()
|
||||
appState.loadDocument(from: firstURL)
|
||||
XCTAssertEqual(appState.documentURL, firstURL)
|
||||
|
||||
let provider = try XCTUnwrap(NSItemProvider(contentsOf: secondURL))
|
||||
XCTAssertTrue(appState.openDroppedDocument(from: [provider]))
|
||||
|
||||
try await waitUntil {
|
||||
appState.documentURL == secondURL
|
||||
}
|
||||
|
||||
XCTAssertEqual(appState.documentURL, secondURL)
|
||||
XCTAssertNotNil(appState.document)
|
||||
XCTAssertFalse(appState.hasUnsavedChanges)
|
||||
}
|
||||
|
||||
func testClosingDocumentReturnsToEmptyWindowWorkflow() throws {
|
||||
let url = try makeTemporaryPDF()
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
|
||||
let appState = AppState()
|
||||
appState.loadDocument(from: url)
|
||||
appState.showLeftSidebar = true
|
||||
appState.leftSidebarMode = .annotations
|
||||
appState.showCommentsSidebar = true
|
||||
appState.sidebarMode = .highlights
|
||||
appState.searchText = "draft"
|
||||
appState.showToolbarSearch = true
|
||||
appState.collapsedPageIndexes = [0]
|
||||
|
||||
appState.closeDocument()
|
||||
|
||||
XCTAssertNil(appState.document)
|
||||
XCTAssertNil(appState.documentURL)
|
||||
XCTAssertFalse(appState.showLeftSidebar)
|
||||
XCTAssertEqual(appState.leftSidebarMode, .pages)
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .annotations)
|
||||
XCTAssertEqual(appState.searchText, "")
|
||||
XCTAssertFalse(appState.showToolbarSearch)
|
||||
XCTAssertTrue(appState.annotations.isEmpty)
|
||||
XCTAssertTrue(appState.bookmarks.isEmpty)
|
||||
XCTAssertEqual(appState.currentPageIndex, 0)
|
||||
XCTAssertEqual(appState.pageText, "1")
|
||||
XCTAssertFalse(appState.hasUnsavedWork)
|
||||
XCTAssertEqual(appState.statusMessage, "Closed PDF.")
|
||||
}
|
||||
|
||||
func testCompactWorkflowShowsOnlyOneSidebarAtATime() {
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(ReaderAdaptiveLayout.minimumWindowWidth)
|
||||
|
||||
appState.toggleRightSidebar(mode: .highlights)
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
XCTAssertFalse(appState.showLeftSidebar)
|
||||
|
||||
appState.togglePageSidebar()
|
||||
XCTAssertTrue(appState.showLeftSidebar)
|
||||
XCTAssertEqual(appState.leftSidebarMode, .pages)
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
|
||||
appState.toggleRightSidebar(mode: .highlights)
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
XCTAssertFalse(appState.showLeftSidebar)
|
||||
}
|
||||
|
||||
func testPageSidebarToggleClosesLeftSidebarEvenWhenMarksAreSelected() {
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_280)
|
||||
appState.showLeftSidebar = true
|
||||
appState.leftSidebarMode = .annotations
|
||||
|
||||
appState.togglePageSidebar()
|
||||
|
||||
XCTAssertFalse(appState.showLeftSidebar)
|
||||
XCTAssertEqual(appState.leftSidebarMode, .annotations)
|
||||
}
|
||||
|
||||
func testRightSidebarToolbarToggleClosesAndReopensCurrentMode() {
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_280)
|
||||
|
||||
appState.toggleRightSidebar(mode: .highlights)
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
|
||||
appState.toggleRightSidebarVisibility()
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
|
||||
appState.toggleRightSidebarVisibility()
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
}
|
||||
|
||||
func testRightSidebarToggleClosesFromDifferentOpenMode() {
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_280)
|
||||
|
||||
appState.toggleRightSidebar(mode: .highlights)
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
|
||||
appState.toggleRightSidebar(mode: .annotations)
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
|
||||
appState.toggleRightSidebar(mode: .annotations)
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .annotations)
|
||||
}
|
||||
|
||||
func testRightSidebarToggleFromHighlightsDoesNotSwitchModeWhenClosing() {
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_280)
|
||||
|
||||
appState.toggleRightSidebar(mode: .highlights)
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
|
||||
appState.toggleRightSidebarVisibility()
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
|
||||
appState.toggleRightSidebarVisibility()
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .highlights)
|
||||
}
|
||||
|
||||
func testRightSidebarToolbarToggleDefaultsToCommentsWhenNoModeWasChosen() {
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_280)
|
||||
|
||||
XCTAssertFalse(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .annotations)
|
||||
|
||||
appState.toggleRightSidebarVisibility()
|
||||
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .annotations)
|
||||
}
|
||||
|
||||
func testRegularWorkflowAllowsNavigationAndReviewSidebarsTogether() {
|
||||
let appState = AppState()
|
||||
appState.updateWindowWidth(1_000)
|
||||
|
||||
appState.togglePageSidebar()
|
||||
appState.toggleRightSidebar(mode: .annotations)
|
||||
|
||||
XCTAssertTrue(appState.showLeftSidebar)
|
||||
XCTAssertEqual(appState.leftSidebarMode, .pages)
|
||||
XCTAssertTrue(appState.showCommentsSidebar)
|
||||
XCTAssertEqual(appState.sidebarMode, .annotations)
|
||||
}
|
||||
|
||||
func testSaveAvailabilityTracksReplyDraftAsUnsavedWork() throws {
|
||||
let url = try makeTemporaryPDF()
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
|
||||
let appState = AppState()
|
||||
appState.loadDocument(from: url)
|
||||
|
||||
XCTAssertFalse(appState.hasUnsavedWork)
|
||||
XCTAssertFalse(appState.canSaveDocument)
|
||||
XCTAssertEqual(appState.saveHelpText, "No unsaved changes.")
|
||||
|
||||
appState.sidebarReplyDraft = "Need to verify this quote."
|
||||
|
||||
XCTAssertTrue(appState.hasUnsavedWork)
|
||||
XCTAssertTrue(appState.hasUnsentSidebarReplyDraft)
|
||||
XCTAssertTrue(appState.canSaveDocument)
|
||||
XCTAssertEqual(appState.saveHelpText, "Send or cancel the reply draft before saving.")
|
||||
|
||||
appState.hasUnsavedChanges = true
|
||||
XCTAssertTrue(appState.canSaveDocument)
|
||||
XCTAssertEqual(appState.saveHelpText, "Save PDF")
|
||||
}
|
||||
|
||||
private func makeTemporaryPDF() throws -> URL {
|
||||
let url = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(UUID().uuidString)
|
||||
.appendingPathExtension("pdf")
|
||||
|
||||
let data = NSMutableData()
|
||||
guard let consumer = CGDataConsumer(data: data) else {
|
||||
throw TestPDFError.couldNotCreateConsumer
|
||||
}
|
||||
|
||||
var mediaBox = CGRect(x: 0, y: 0, width: 612, height: 792)
|
||||
guard let context = CGContext(consumer: consumer, mediaBox: &mediaBox, nil) else {
|
||||
throw TestPDFError.couldNotCreateContext
|
||||
}
|
||||
|
||||
context.beginPDFPage(nil)
|
||||
context.endPDFPage()
|
||||
context.closePDF()
|
||||
|
||||
try data.write(to: url, options: .atomic)
|
||||
return url
|
||||
}
|
||||
|
||||
private enum TestPDFError: Error {
|
||||
case couldNotCreateConsumer
|
||||
case couldNotCreateContext
|
||||
}
|
||||
|
||||
private func waitUntil(
|
||||
timeout: TimeInterval = 2,
|
||||
condition: @MainActor @escaping () -> Bool
|
||||
) async throws {
|
||||
let deadline = Date().addingTimeInterval(timeout)
|
||||
while Date() < deadline {
|
||||
if condition() {
|
||||
return
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 20_000_000)
|
||||
}
|
||||
XCTFail("Timed out waiting for condition.")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user