A dependency-free Swift library that renders org-mode to sanitized HTML.

html library org-mode swift

Tests/OrgSwiftTests/ConformanceTests.swift

main
org-swift/Tests/OrgSwiftTests/ConformanceTests.swift history · blame · raw

117 lines · 5563 bytes

  1import Foundation
  2import Testing
  3@testable import OrgSwift
  4
  5/// Locate the org-conformance corpus: env var first, then the sibling checkout that sits
  6/// next to this package under a shared parent (/org-swift and /org-conformance).
  7private func corpusCasesDir() -> URL? {
  8    if let env = ProcessInfo.processInfo.environment["ORG_CONFORMANCE_DIR"] {
  9        let cases = URL(fileURLWithPath: env).appendingPathComponent("cases")
 10        if FileManager.default.fileExists(atPath: cases.path) { return cases }
 11    }
 12    // #filePath = /org-swift/Tests/OrgSwiftTests/ConformanceTests.swift
 13    let pkgRoot = URL(fileURLWithPath: #filePath)
 14        .deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
 15    let sibling = pkgRoot.deletingLastPathComponent()
 16        .appendingPathComponent("org-conformance").appendingPathComponent("cases")
 17    if FileManager.default.fileExists(atPath: sibling.path) { return sibling }
 18    return nil
 19}
 20
 21private func caseNames(_ dir: URL) -> [String] {
 22    let items = (try? FileManager.default.contentsOfDirectory(atPath: dir.path)) ?? []
 23    return items.filter { $0.hasSuffix(".org") }.map { String($0.dropLast(4)) }.sorted()
 24}
 25
 26private func read(_ dir: URL, _ name: String, _ ext: String) -> String {
 27    (try? String(contentsOf: dir.appendingPathComponent("\(name).\(ext)"), encoding: .utf8)) ?? ""
 28}
 29
 30private func goldenSkeleton(_ dir: URL, _ name: String) -> [String] {
 31    let raw = read(dir, name, "skeleton")
 32    let trimmed = raw.hasSuffix("\n") ? String(raw.dropLast()) : raw
 33    return trimmed.isEmpty ? [] : trimmed.components(separatedBy: "\n")
 34}
 35
 36/// orgo's `render()` produces body content only: `#+TITLE`/`#+AUTHOR`/`#+DATE` are document
 37/// metadata carried by the page template, and a top-level `*` heading is `<h2>` because the
 38/// title owns `<h1>`. The corpus goldens are that body content, so the renderer is measured
 39/// in the matching configuration.
 40private let orgoCompatibleOptions = OrgRenderOptions(metadataHeader: false, headingLevelOffset: 1)
 41
 42/// What each corpus case does against orgo today. This is a *reviewed* record, not a wish:
 43/// a case that starts matching, or a matching case that regresses, both fail the test and
 44/// demand this map be updated  which is the point. Each `.diverges` reason names the
 45/// missing capability, and together they are OrgSwift's conformance backlog (see GAPS.md).
 46private enum Expectation {
 47    case matches
 48    case diverges(String)
 49}
 50
 51private let expectations: [String: Expectation] = [
 52    "table": .matches,
 53    "blocks": .matches,
 54    "core": .matches,
 55    "elements": .matches,
 56    "footnote": .matches,
 57    "headings": .matches,
 58    "images": .matches,
 59    "lists": .matches,
 60    "minimal": .matches,
 61    "outofscope": .diverges("out-of-scope constructs; #+INCLUDE and drawers leak — orgo may differ here too"),
 62    "tblfm": .matches,
 63    "timestamps": .matches,
 64]
 65
 66struct ConformanceTests {
 67    /// The skeleton port must be byte-identical to orgo's, or comparing renderers means
 68    /// nothing. Prove it against the reference HTML first: feed each corpus `.html` through
 69    /// the Swift port and require it to reproduce the checked-in `.skeleton`. A failure here
 70    /// is a port bug, isolated from any renderer question.
 71    @Test
 72    func skeletonPortMatchesGoldens() throws {
 73        guard let dir = corpusCasesDir() else {
 74            print("org-conformance corpus not found — skipping (set ORG_CONFORMANCE_DIR)")
 75            return
 76        }
 77        for name in caseNames(dir) {
 78            let got = OrgSkeleton.skeleton(read(dir, name, "html"))
 79            #expect(got == goldenSkeleton(dir, name), "skeleton port diverges from orgo on '\(name)'")
 80        }
 81    }
 82
 83    /// Render every case with OrgSwift and check the result against the reviewed
 84    /// expectation map. Green when reality matches the record; a case that newly conforms
 85    /// (a gap closed) or newly diverges (a regression) fails and points at the map.
 86    @Test
 87    func renderConformanceMatchesExpectation() throws {
 88        guard let dir = corpusCasesDir() else {
 89            print("org-conformance corpus not found — skipping (set ORG_CONFORMANCE_DIR)")
 90            return
 91        }
 92        let dump = ProcessInfo.processInfo.environment["ORG_DUMP"] != nil
 93        for name in caseNames(dir) {
 94            let expected = goldenSkeleton(dir, name)
 95            let got = OrgSkeleton.skeleton(OrgRenderer.renderToHTML(read(dir, name, "org"), options: orgoCompatibleOptions))
 96            let conforms = got == expected
 97
 98            switch expectations[name] {
 99            case .matches:
100                #expect(conforms, "'\(name)' was expected to match orgo but diverged")
101            case .diverges(let reason):
102                #expect(!conforms, "'\(name)' now MATCHES orgo — gap closed (\(reason)). Move it to .matches in expectations.")
103            case nil:
104                Issue.record("'\(name)' has no entry in the expectations map")
105            }
106
107            if dump && (conforms != (expectations[name].map { if case .matches = $0 { true } else { false } } ?? false)) {
108                let maxN = max(got.count, expected.count)
109                print("---- DIFF \(name) ----")
110                for k in 0..<maxN where (k < expected.count ? expected[k] : "") != (k < got.count ? got[k] : "") {
111                    print("  orgo[\(k)]=\(k < expected.count ? expected[k] : "")")
112                    print("  ours[\(k)]=\(k < got.count ? got[k] : "")")
113                }
114            }
115        }
116    }
117}