Skip to content

Commit d46d984

Browse files
authored
Add integration tests for PreviewSession.compile() with BuildContext (#39)
1 parent 625c55a commit d46d984

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import Foundation
2+
import Testing
3+
4+
@testable import PreviewsCore
5+
6+
@Suite("PreviewSession with BuildContext", .serialized)
7+
struct PreviewSessionBuildContextTests {
8+
9+
// MARK: - Paths
10+
11+
static let repoRoot: URL = URL(fileURLWithPath: #filePath)
12+
.deletingLastPathComponent() // PreviewsCoreTests/
13+
.deletingLastPathComponent() // Tests/
14+
.deletingLastPathComponent() // repo root
15+
16+
static let spmExampleRoot = repoRoot.appendingPathComponent("examples/spm")
17+
static let toDoViewFile = spmExampleRoot.appendingPathComponent("Sources/ToDo/ToDoView.swift")
18+
19+
// MARK: - Helpers
20+
21+
static func buildSPMExample() async throws -> BuildContext {
22+
guard let spm = try await SPMBuildSystem.detect(for: toDoViewFile) else {
23+
throw TestHelperError.noBuildSystem
24+
}
25+
return try await spm.build(platform: .macOS)
26+
}
27+
28+
static func tier1Context(from ctx: BuildContext) -> BuildContext {
29+
BuildContext(
30+
moduleName: ctx.moduleName,
31+
compilerFlags: ctx.compilerFlags,
32+
projectRoot: ctx.projectRoot,
33+
targetName: ctx.targetName,
34+
sourceFiles: nil
35+
)
36+
}
37+
38+
enum TestHelperError: Error, LocalizedError {
39+
case noBuildSystem
40+
41+
var errorDescription: String? {
42+
switch self {
43+
case .noBuildSystem:
44+
return "SPMBuildSystem.detect returned nil for examples/spm"
45+
}
46+
}
47+
}
48+
49+
// MARK: - Tests
50+
51+
@Test("Tier 2 compile: dylib + populated literals + DesignTimeStore symbols")
52+
func tier2Compile() async throws {
53+
let ctx = try await Self.buildSPMExample()
54+
#expect(ctx.supportsTier2)
55+
#expect(
56+
ctx.sourceFiles?.contains(where: { $0.lastPathComponent == "Item.swift" }) == true)
57+
58+
let compiler = try await Compiler()
59+
let session = PreviewSession(
60+
sourceFile: Self.toDoViewFile,
61+
previewIndex: 0,
62+
compiler: compiler,
63+
buildContext: ctx
64+
)
65+
66+
let result = try await session.compile()
67+
#expect(FileManager.default.fileExists(atPath: result.dylibPath.path))
68+
#expect(!result.literals.isEmpty, "Tier 2 should produce literal mappings")
69+
70+
let loader = try DylibLoader(path: result.dylibPath.path)
71+
typealias CreateFunc = @convention(c) () -> UnsafeMutableRawPointer
72+
let _: CreateFunc = try loader.symbol(name: "createPreviewView")
73+
typealias SetString = @convention(c) (UnsafePointer<CChar>, UnsafePointer<CChar>) -> Void
74+
let _: SetString = try loader.symbol(name: "designTimeSetString")
75+
typealias SetInt = @convention(c) (UnsafePointer<CChar>, Int) -> Void
76+
let _: SetInt = try loader.symbol(name: "designTimeSetInteger")
77+
}
78+
79+
@Test("Tier 1 context has supportsTier2 == false")
80+
func tier1ContextProperties() {
81+
let ctx = BuildContext(
82+
moduleName: "TestModule",
83+
compilerFlags: ["-I", "/some/path"],
84+
projectRoot: URL(fileURLWithPath: "/tmp"),
85+
targetName: "TestModule",
86+
sourceFiles: nil
87+
)
88+
#expect(!ctx.supportsTier2)
89+
#expect(ctx.sourceFiles == nil)
90+
}
91+
92+
@Test("tryLiteralUpdate returns nil for Tier 1 session")
93+
func tryLiteralUpdateNilForTier1() async throws {
94+
let fullCtx = try await Self.buildSPMExample()
95+
let ctx = Self.tier1Context(from: fullCtx)
96+
97+
let compiler = try await Compiler()
98+
let session = PreviewSession(
99+
sourceFile: Self.toDoViewFile,
100+
previewIndex: 0,
101+
compiler: compiler,
102+
buildContext: ctx
103+
)
104+
105+
// No compile needed — tryLiteralUpdate checks buildContext.supportsTier2
106+
// before checking lastOriginalSource
107+
let original = try String(contentsOf: Self.toDoViewFile, encoding: .utf8)
108+
#expect(original.contains("\"My Items\""), "ToDoView.swift should contain \"My Items\"")
109+
let modified = original.replacingOccurrences(of: "\"My Items\"", with: "\"My Tasks\"")
110+
111+
let changes = await session.tryLiteralUpdate(newSource: modified)
112+
#expect(changes == nil, "Tier 1 should always return nil from tryLiteralUpdate")
113+
}
114+
115+
@Test("tryLiteralUpdate returns changes for Tier 2 session")
116+
func tryLiteralUpdateChangesForTier2() async throws {
117+
let ctx = try await Self.buildSPMExample()
118+
119+
let compiler = try await Compiler()
120+
let session = PreviewSession(
121+
sourceFile: Self.toDoViewFile,
122+
previewIndex: 0,
123+
compiler: compiler,
124+
buildContext: ctx
125+
)
126+
127+
_ = try await session.compile()
128+
129+
let original = try String(contentsOf: Self.toDoViewFile, encoding: .utf8)
130+
#expect(original.contains("\"My Items\""), "ToDoView.swift should contain \"My Items\"")
131+
let modified = original.replacingOccurrences(of: "\"My Items\"", with: "\"My Tasks\"")
132+
133+
let changes = await session.tryLiteralUpdate(newSource: modified)
134+
#expect(changes != nil, "Tier 2 should detect literal-only change")
135+
#expect(
136+
changes?.contains(where: { $0.newValue == .string("My Tasks") }) == true,
137+
"Should contain the changed string value"
138+
)
139+
}
140+
}

0 commit comments

Comments
 (0)