krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Tests/OrgCoreTests/TextStatsTests.swift

778058aafc7ddacee4a613b1da87c60aa0c8e113
orgstar/Tests/OrgCoreTests/TextStatsTests.swift history · blame · raw

79 lines · 4364 bytes

6 symbols in this file
 1import Foundation
 2import Testing
 3@testable import OrgCore
 4
 5struct TextStatsTests {
 6    static let samples = [
 7        "don't stop", "foo_bar baz", "x-y", "café au lait", "日本語 text", "*bold* /it/ =code=", "2026-10-05 Mon",
 8        "https://example.com/a-b?c=1", "[[file:x.org][Desc here]]", "* TODO Heading :tag:work:", "a.b,c;d",
 9        "it's 3.14 or 1e5", "naïve—dash", "emoji 😀 ok", "über_cool", "«quoted» “smart” ‘single’", "a\u{00A0}b",
10        "\tindented\ttext", "- [X] item\n  - sub 2\n", "x × y ÷ z → w", "€5 £3 ©2026 °C ™ ¶ § •", "✓ done ★ star", "¶ § · µ ¬ ¨ ■ ♥ ⌘ ✂ ➜ 🎉", "line one\r\nline two\r\n", "Sony α7 III", "漢字かなカナ ひらがなカタカナ", "e\u{0301}te résumé", "Привет мир", "a\u{0080}\u{0098}b", "",
11    ]
12
13    /// `count-words`, `count-lines` and `current-column` at the end of each sample in an org buffer.
14    static func emacs(_ samples: [String]) throws -> [String] {
15        let folder = FileManager.default.temporaryDirectory.appendingPathComponent("orgstar-stats-\(UUID().uuidString)")
16        try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
17        defer { try? FileManager.default.removeItem(at: folder) }
18        let input = folder.appendingPathComponent("in.json")
19        let output = folder.appendingPathComponent("out.json")
20        let script = folder.appendingPathComponent("stats.el")
21        try JSONEncoder().encode(samples).write(to: input)
22        try """
23        ;;; -*- lexical-binding: t -*-
24        (require 'org)
25        (require 'json)
26        (let ((samples (append (json-read-file (getenv "ORACLE_INPUT")) nil)) result)
27          (dolist (s samples)
28            (with-temp-buffer
29              (org-mode)
30              (insert s)
31              (push (format "%d words %d lines %d chars, %d:%d"
32                            (count-words (point-min) (point-max))
33                            (count-lines (point-min) (point-max))
34                            (- (point-max) (point-min))
35                            (line-number-at-pos) (current-column))
36                    result)))
37          (let ((coding-system-for-write 'utf-8-unix))
38            (with-temp-file (getenv "ORACLE_OUTPUT")
39              (insert (json-encode (vconcat (nreverse result)))))))
40        """.write(to: script, atomically: true, encoding: .utf8)
41        let log = try EmacsOracle.run(["-Q", "--batch", "-l", script.path], environment: ["ORACLE_INPUT": input.path, "ORACLE_OUTPUT": output.path])
42        guard let data = try? Data(contentsOf: output) else { throw AgendaOracle.OracleError(log: log) }
43        return try JSONDecoder().decode([String].self, from: data)
44    }
45
46    static func ours(_ s: String) -> String {
47        let region = TextStats.region(s)
48        let position = TextStats.position(in: s, at: s.utf16.count)
49        return "\(region.words) words \(region.lines) lines \(region.characters) chars, \(position.line):\(position.column)"
50    }
51
52    @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
53    func matchesEmacs() throws {
54        let emacs = try Self.emacs(Self.samples)
55        for (sample, expected) in zip(Self.samples, emacs) {
56            #expect(Self.ours(sample) == expected, "\(sample)")
57        }
58    }
59
60    /// `ORGSTAR_ORACLE_CORPUS=<folder>`: every org file under the folder.
61    @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_ORACLE_CORPUS"] != nil))
62    func corpusMatchesEmacs() throws {
63        let root = URL(fileURLWithPath: ProcessInfo.processInfo.environment["ORGSTAR_ORACLE_CORPUS"]!)
64        let files = FileManager.default.enumerator(at: root, includingPropertiesForKeys: nil)!
65            .compactMap { $0 as? URL }.filter { $0.pathExtension == "org" }
66        let texts = files.compactMap { try? String(contentsOf: $0, encoding: .utf8) }
67        let emacs = try Self.emacs(texts)
68        for (text, expected) in zip(texts, emacs) {
69            #expect(Self.ours(text) == expected, "\(text.prefix(60))")
70        }
71    }
72
73    @Test func positionMidLine() {
74        let text = "* Heading\n\tab日本\n"
75        let offset = (text as NSString).range(of: "本").location
76        #expect(TextStats.position(in: text, at: offset) == (2, 12))
77        #expect(TextStats.position(in: text, at: 0) == (1, 0))
78    }
79}