Tests/OrgCoreTests/TableTests.swift
101 lines · 5482 bytes
6 symbols in this file
1import Foundation
2import Testing
3@testable import OrgCore
4
5struct TableTests {
6 @Test func aligns() {
7 let result = runCommand(TableCommand(.align), "|a|bb|\n|ccc|1|\n", caret: 1).text
8 #expect(result == "| a | bb |\n| ccc | 1 |\n")
9 }
10
11 static let variants = [
12 "|a|bb|\n|ccc|1|\n",
13 "| Name | Qty |\n|-\n| apple | 3 |\n| pear | 12 |\n",
14 " | x | y |\n |---+---|\n | 1 | 2 |\n",
15 "| [[https://example.com][link]] | *bold* | =code= |\n| a | b | c |\n",
16 "| \\\\alpha | a_{bc} | x^2 |\n| 1 | 2 | 3 |\n",
17 "| <r> | <c5> |\n| a | b |\n",
18 "| 1 | 2 |\n| 3 | 4 |\n#+TBLFM: $2=$1*2::@2$1=7\n",
19 "text\n| a |\n| b |\nmore\n",
20 "| ü | 漢字 |\n| x | y |\n",
21 "| a | b\n| c |\n",
22 "|-+-|\n",
23 ]
24
25 static let commands: [(command: any OrgCommand, form: String, headingLineOnly: Bool)] = [
26 (TableCommand(.align), "(org-table-align)", false),
27 (TableCommand(.nextField), "(org-table-next-field)", false),
28 (TableCommand(.previousField), "(org-table-previous-field)", false),
29 (TableCommand(.nextRow), "(org-table-next-row)", false),
30 (TableCommand(.moveRowUp), "(org-table-move-row-up)", false),
31 (TableCommand(.moveRowDown), "(org-table-move-row-down)", false),
32 (TableCommand(.moveColumnLeft), "(org-table-move-column-left)", false),
33 (TableCommand(.moveColumnRight), "(org-table-move-column-right)", false),
34 (TableCommand(.insertColumn), "(org-table-insert-column)", false),
35 (TableCommand(.deleteColumn), "(org-table-delete-column)", false),
36 (TableCommand(.killRow), "(org-table-kill-row)", false),
37 (TableCommand(.insertRowAbove), "(org-table-insert-row)", false),
38 (TableCommand(.insertHline), "(org-table-insert-hline)", false),
39 ]
40
41 static func tableCarets(_ text: String) -> [Int] {
42 EmacsOracle.positions(text).filter { offset in
43 let ns = text as NSString
44 let line = ns.lineRange(for: NSRange(location: min(offset, max(0, ns.length - 1)), length: 0))
45 return offset < ns.length && ns.substring(with: line).trimmingCharacters(in: .whitespaces).hasPrefix("|")
46 }
47 }
48
49 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
50 func tableCommandsMatchEmacs() throws {
51 let inputs = Self.variants.map { ($0, Self.tableCarets($0)) }
52 try compareWithEmacs(inputs, Self.commands)
53 try compareWithEmacs(inputs, Self.commands, options: doomOptions)
54 }
55
56 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
57 func createTableMatchesEmacs() throws {
58 for (text, caret) in [("", 0), ("x\n", 1), (" \n", 2)] {
59 for size in ["3x2", "1x1", "2x3"] {
60 let emacs = try EmacsOracle.run([EmacsOracle.Case(text: text, point: caret + 1, form: "(org-table-create \"\(size)\")")])[0]
61 let ours = runCommand(CreateTable(), text, caret: caret, answers: ["size": size])
62 #expect(ours.text == emacs.text && ours.caret == EmacsOracle.offset(emacs.point, in: emacs.text), "\(size) in \(text.debugDescription): ours \(ours.text.debugDescription)@\(ours.caret) emacs \(emacs.text.debugDescription)@\(EmacsOracle.offset(emacs.point, in: emacs.text))")
63 }
64 }
65 }
66
67 /// Real tables: `ORGSTAR_ORACLE_CORPUS=<folder>` aligns and TABs in the first cell of up to
68 /// eight table rows per file, in up to 60 files.
69 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_ORACLE_CORPUS"] != nil))
70 func tablesMatchEmacsOnACorpus() throws {
71 let root = URL(fileURLWithPath: ProcessInfo.processInfo.environment["ORGSTAR_ORACLE_CORPUS"]!)
72 let files = FileManager.default.enumerator(at: root, includingPropertiesForKeys: nil)!
73 .compactMap { $0 as? URL }.filter { $0.pathExtension == "org" }.sorted { $0.path < $1.path }
74 var samples: [(String, [Int])] = []
75 for file in files where samples.count < 60 {
76 guard let text = try? String(contentsOf: file, encoding: .utf8), text.utf16.count <= 30_000, !text.contains("\r") else { continue }
77 let ns = text as NSString
78 var carets: [Int] = []
79 var location = 0
80 var inBlock = false
81 while location < ns.length, carets.count < 8 {
82 let line = ns.lineRange(for: NSRange(location: location, length: 0))
83 let content = ns.substring(with: line)
84 let trimmed = content.trimmingCharacters(in: .whitespaces).lowercased()
85 if trimmed.hasPrefix("#+begin_") { inBlock = true }
86 if trimmed.hasPrefix("#+end_") { inBlock = false }
87 if !inBlock, trimmed.hasPrefix("|"), let bar = content.firstIndex(of: "|") {
88 carets.append(line.location + content.distance(from: content.startIndex, to: bar) + 2)
89 }
90 location = NSMaxRange(line)
91 }
92 if !carets.isEmpty { samples.append((text, carets)) }
93 }
94 let commands: [(command: any OrgCommand, form: String, headingLineOnly: Bool)] = [
95 (TableCommand(.align), "(org-table-align)", false),
96 (TableCommand(.nextField), "(org-table-next-field)", false),
97 ]
98 try compareWithEmacs(samples, commands)
99 try compareWithEmacs(samples, commands, options: doomOptions)
100 }
101}