Skip to content

Commit 5f20691

Browse files
myrisonclaude
andcommitted
Add DocumentState tests for file change detection and reload behavior
Cover the file monitor's change detection pipeline that the auto-raise feature depends on: file modification sets fileChanged, no-modification leaves it unset, loadFile resets it, and reload guards against nil URL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f836b00 commit 5f20691

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import XCTest
2+
@testable import MarkdownViewer
3+
4+
final class DocumentStateTests: XCTestCase {
5+
6+
// MARK: - File Change Detection
7+
//
8+
// These tests verify that DocumentState's file monitor correctly
9+
// detects external file modifications and surfaces them via the
10+
// published fileChanged flag. The auto-raise feature (added in
11+
// this PR) depends on this detection path to decide whether to
12+
// bring the window to front.
13+
14+
func testFileChangedSetToTrueWhenMonitoredFileIsModified() throws {
15+
let file = try TemporaryFile.create(named: "test.md")
16+
defer { file.cleanup() }
17+
18+
try "# Hello".write(to: file.url, atomically: true, encoding: .utf8)
19+
20+
let state = DocumentState()
21+
state.loadFile(at: file.url)
22+
23+
XCTAssertFalse(state.fileChanged, "fileChanged should be false immediately after load")
24+
25+
// Modify the file after a brief delay so the modification date differs
26+
let expectation = expectation(description: "File change detected")
27+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
28+
try? "# Updated".write(to: file.url, atomically: true, encoding: .utf8)
29+
}
30+
31+
// Give the file monitor time to detect the change
32+
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
33+
expectation.fulfill()
34+
}
35+
36+
wait(for: [expectation], timeout: 5.0)
37+
XCTAssertTrue(state.fileChanged, "fileChanged should be true after file modification")
38+
}
39+
40+
func testFileChangedNotSetWhenFileIsNotModified() throws {
41+
let file = try TemporaryFile.create(named: "stable.md")
42+
defer { file.cleanup() }
43+
44+
try "# Stable".write(to: file.url, atomically: true, encoding: .utf8)
45+
46+
let state = DocumentState()
47+
state.loadFile(at: file.url)
48+
49+
let expectation = expectation(description: "Wait period")
50+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
51+
expectation.fulfill()
52+
}
53+
54+
wait(for: [expectation], timeout: 3.0)
55+
XCTAssertFalse(state.fileChanged, "fileChanged should remain false when file is not modified")
56+
}
57+
58+
func testLoadFileResetsFileChangedFlag() throws {
59+
let file = try TemporaryFile.create(named: "reload.md")
60+
defer { file.cleanup() }
61+
62+
try "# First".write(to: file.url, atomically: true, encoding: .utf8)
63+
64+
let state = DocumentState()
65+
state.loadFile(at: file.url)
66+
67+
// Trigger a file change
68+
let changeExpectation = expectation(description: "File change detected")
69+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
70+
try? "# Modified".write(to: file.url, atomically: true, encoding: .utf8)
71+
}
72+
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
73+
changeExpectation.fulfill()
74+
}
75+
wait(for: [changeExpectation], timeout: 5.0)
76+
XCTAssertTrue(state.fileChanged)
77+
78+
// Reload should reset the flag
79+
state.loadFile(at: file.url)
80+
XCTAssertFalse(state.fileChanged, "loadFile should reset fileChanged to false")
81+
}
82+
83+
// MARK: - Reload Behavior
84+
85+
func testReloadDoesNothingWhenNoFileIsLoaded() {
86+
let state = DocumentState()
87+
XCTAssertNil(state.currentURL)
88+
let previousToken = state.reloadToken
89+
90+
state.reload()
91+
92+
// reloadToken should not change when there is no file to reload
93+
XCTAssertEqual(state.reloadToken, previousToken,
94+
"reload() with no currentURL should not generate a new reloadToken")
95+
}
96+
97+
func testReloadUpdatesReloadToken() throws {
98+
let file = try TemporaryFile.create(named: "doc.md")
99+
defer { file.cleanup() }
100+
101+
try "# Doc".write(to: file.url, atomically: true, encoding: .utf8)
102+
103+
let state = DocumentState()
104+
state.loadFile(at: file.url)
105+
106+
let tokenBefore = state.reloadToken
107+
state.reload()
108+
109+
XCTAssertNotNil(state.reloadToken)
110+
XCTAssertNotEqual(state.reloadToken, tokenBefore,
111+
"reload() should produce a new reloadToken")
112+
}
113+
}

0 commit comments

Comments
 (0)