import Foundation import Testing @testable import OrgSwift /// Locate the org-conformance corpus: env var first, then the sibling checkout that sits /// next to this package under a shared parent (…/org-swift and …/org-conformance). private func corpusCasesDir() -> URL? { if let env = ProcessInfo.processInfo.environment["ORG_CONFORMANCE_DIR"] { let cases = URL(fileURLWithPath: env).appendingPathComponent("cases") if FileManager.default.fileExists(atPath: cases.path) { return cases } } // #filePath = …/org-swift/Tests/OrgSwiftTests/ConformanceTests.swift let pkgRoot = URL(fileURLWithPath: #filePath) .deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() let sibling = pkgRoot.deletingLastPathComponent() .appendingPathComponent("org-conformance").appendingPathComponent("cases") if FileManager.default.fileExists(atPath: sibling.path) { return sibling } return nil } private func caseNames(_ dir: URL) -> [String] { let items = (try? FileManager.default.contentsOfDirectory(atPath: dir.path)) ?? [] return items.filter { $0.hasSuffix(".org") }.map { String($0.dropLast(4)) }.sorted() } private func read(_ dir: URL, _ name: String, _ ext: String) -> String { (try? String(contentsOf: dir.appendingPathComponent("\(name).\(ext)"), encoding: .utf8)) ?? "" } private func goldenSkeleton(_ dir: URL, _ name: String) -> [String] { let raw = read(dir, name, "skeleton") let trimmed = raw.hasSuffix("\n") ? String(raw.dropLast()) : raw return trimmed.isEmpty ? [] : trimmed.components(separatedBy: "\n") } /// orgo's `render()` produces body content only: `#+TITLE`/`#+AUTHOR`/`#+DATE` are document /// metadata carried by the page template, and a top-level `*` heading is `

` because the /// title owns `

`. The corpus goldens are that body content, so the renderer is measured /// in the matching configuration. private let orgoCompatibleOptions = OrgRenderOptions(metadataHeader: false, headingLevelOffset: 1) /// What each corpus case does against orgo today. This is a *reviewed* record, not a wish: /// a case that starts matching, or a matching case that regresses, both fail the test and /// demand this map be updated — which is the point. Each `.diverges` reason names the /// missing capability, and together they are OrgSwift's conformance backlog (see GAPS.md). private enum Expectation { case matches case diverges(String) } private let expectations: [String: Expectation] = [ "table": .matches, "blocks": .matches, "core": .matches, "elements": .matches, "footnote": .matches, "headings": .matches, "images": .matches, "lists": .matches, "minimal": .matches, "outofscope": .diverges("out-of-scope constructs; #+INCLUDE and drawers leak — orgo may differ here too"), "tblfm": .matches, "timestamps": .matches, ] struct ConformanceTests { /// The skeleton port must be byte-identical to orgo's, or comparing renderers means /// nothing. Prove it against the reference HTML first: feed each corpus `.html` through /// the Swift port and require it to reproduce the checked-in `.skeleton`. A failure here /// is a port bug, isolated from any renderer question. @Test func skeletonPortMatchesGoldens() throws { guard let dir = corpusCasesDir() else { print("org-conformance corpus not found — skipping (set ORG_CONFORMANCE_DIR)") return } for name in caseNames(dir) { let got = OrgSkeleton.skeleton(read(dir, name, "html")) #expect(got == goldenSkeleton(dir, name), "skeleton port diverges from orgo on '\(name)'") } } /// Render every case with OrgSwift and check the result against the reviewed /// expectation map. Green when reality matches the record; a case that newly conforms /// (a gap closed) or newly diverges (a regression) fails and points at the map. @Test func renderConformanceMatchesExpectation() throws { guard let dir = corpusCasesDir() else { print("org-conformance corpus not found — skipping (set ORG_CONFORMANCE_DIR)") return } let dump = ProcessInfo.processInfo.environment["ORG_DUMP"] != nil for name in caseNames(dir) { let expected = goldenSkeleton(dir, name) let got = OrgSkeleton.skeleton(OrgRenderer.renderToHTML(read(dir, name, "org"), options: orgoCompatibleOptions)) let conforms = got == expected switch expectations[name] { case .matches: #expect(conforms, "'\(name)' was expected to match orgo but diverged") case .diverges(let reason): #expect(!conforms, "'\(name)' now MATCHES orgo — gap closed (\(reason)). Move it to .matches in expectations.") case nil: Issue.record("'\(name)' has no entry in the expectations map") } if dump && (conforms != (expectations[name].map { if case .matches = $0 { true } else { false } } ?? false)) { let maxN = max(got.count, expected.count) print("---- DIFF \(name) ----") for k in 0..