1
0

cli_test.cljs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. (ns ^:node-only logseq.graph-parser.cli-test
  2. (:require [cljs.test :refer [deftest is testing async use-fixtures]]
  3. [logseq.graph-parser.cli :as gp-cli]
  4. [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
  5. [clojure.string :as string]
  6. [datascript.core :as d]
  7. [logseq.db.sqlite.cli :as sqlite-cli]
  8. [logseq.graph-parser.db :as gp-db]
  9. [clojure.set :as set]
  10. ["fs" :as fs]
  11. ["process" :as process]
  12. ["path" :as path]
  13. [logseq.db.frontend.order :as db-order]))
  14. (use-fixtures
  15. :each
  16. ;; Cleaning tmp/ before leaves last tmp/ after a test run for dev and debugging
  17. {:before
  18. #(async done
  19. (if (fs/existsSync "tmp")
  20. (fs/rm "tmp" #js {:recursive true} (fn [err]
  21. (when err (js/console.log err))
  22. (done)))
  23. (done)))})
  24. ;; Integration test that test parsing a large graph like docs
  25. (deftest ^:integration parse-graph
  26. (let [graph-dir "test/docs-0.9.2"
  27. _ (docs-graph-helper/clone-docs-repo-if-not-exists graph-dir "v0.9.2")
  28. {:keys [conn files asts]} (gp-cli/parse-graph graph-dir {:verbose false})]
  29. (docs-graph-helper/docs-graph-assertions @conn graph-dir files)
  30. (testing "Additional counts"
  31. (is (= 48767 (count (d/datoms @conn :eavt))) "Correct datoms count"))
  32. (testing "Asts"
  33. (is (seq asts) "Asts returned are non-zero")
  34. (is (= files (map :file asts))
  35. "There's an ast returned for every file processed")
  36. (is (empty? (remove #(or
  37. (seq (:ast %))
  38. ;; edn files don't have ast
  39. (string/ends-with? (:file %) ".edn")
  40. ;; logseq files don't have ast
  41. ;; could also used common-config but API isn't public yet
  42. (string/includes? (:file %) (str graph-dir "/logseq/")))
  43. asts))
  44. "Parsed files shouldn't have empty asts"))))
  45. (defn- create-logseq-graph
  46. "Creates a minimal mock graph"
  47. [dir]
  48. (fs/mkdirSync (path/join dir "logseq") #js {:recursive true})
  49. (fs/mkdirSync (path/join dir "journals"))
  50. (fs/mkdirSync (path/join dir "pages")))
  51. (deftest build-graph-files
  52. (create-logseq-graph "tmp/test-graph")
  53. ;; Create files that are recognized
  54. (fs/writeFileSync "tmp/test-graph/pages/foo.md" "")
  55. (fs/writeFileSync "tmp/test-graph/journals/2023_05_09.md" "")
  56. ;; Create file that are ignored and filtered out
  57. (fs/writeFileSync "tmp/test-graph/pages/foo.json" "")
  58. (fs/mkdirSync (path/join "tmp/test-graph" "logseq" "bak"))
  59. (fs/writeFileSync "tmp/test-graph/logseq/bak/baz.md" "")
  60. (testing "ignored files from common-graph"
  61. (is (= (map #(path/join (process/cwd) "tmp/test-graph" %) ["journals/2023_05_09.md" "pages/foo.md"])
  62. (map :file/path (#'gp-cli/build-graph-files (path/resolve "tmp/test-graph") {})))
  63. "Correct paths returned for absolute dir")
  64. (process/chdir "tmp/test-graph")
  65. (is (= (map #(path/join (process/cwd) %) ["journals/2023_05_09.md" "pages/foo.md"])
  66. (map :file/path (#'gp-cli/build-graph-files "." {})))
  67. "Correct paths returned for relative current dir")
  68. (process/chdir "../.."))
  69. (testing ":hidden config"
  70. (fs/mkdirSync (path/join "tmp/test-graph" "script"))
  71. (fs/writeFileSync "tmp/test-graph/script/README.md" "")
  72. (is (= (map #(path/join (process/cwd) "tmp/test-graph" %) ["journals/2023_05_09.md" "pages/foo.md"])
  73. (map :file/path (#'gp-cli/build-graph-files "tmp/test-graph" {:hidden ["script"]})))
  74. "Correct paths returned")))
  75. (defn- create-file-db
  76. "Creates a file-based graph given a map of pages to blocks and returns a datascript db"
  77. [graph-dir pages-to-blocks]
  78. (fs/mkdirSync (path/join graph-dir "pages") #js {:recursive true})
  79. (fs/mkdirSync (path/join graph-dir "journals"))
  80. (doseq [[page blocks] pages-to-blocks]
  81. (fs/writeFileSync (if (contains? (:block/type page) "journal")
  82. ;; Hardcode journal name until more are added
  83. (path/join graph-dir "journals" "2023_07_20.md")
  84. (path/join graph-dir "pages" (str (:block/name page) ".md")))
  85. (string/join "\n" (map #(str "- " (:block/content %)) blocks))))
  86. (let [{:keys [conn]} (gp-cli/parse-graph graph-dir {:verbose false})] @conn))
  87. (defn- create-frontend-blocks
  88. "For a map of pages to their blocks, this creates frontend blocks assuming only top level blocks
  89. are desired. Anything more complex starts to recreate outliner namespaces"
  90. [pages-to-blocks]
  91. (let [page-count (atom 0)
  92. new-db-id #(swap! page-count inc)
  93. created-at (js/Date.now)]
  94. (vec
  95. (mapcat
  96. (fn [[page blocks]]
  97. (let [page-id (new-db-id)
  98. page-uuid (random-uuid)]
  99. (into [(merge page
  100. {:db/id page-id
  101. :block/uuid page-uuid
  102. :block/original-name (string/capitalize (:block/name page))
  103. :block/created-at created-at
  104. :block/updated-at created-at})]
  105. (mapv #(merge %
  106. {:db/id (new-db-id)
  107. :block/uuid (random-uuid)
  108. :block/format :markdown
  109. :block/page {:db/id page-id}
  110. :block/order (db-order/gen-key nil)
  111. :block/parent {:db/id page-id}
  112. :block/created-at created-at
  113. :block/updated-at created-at})
  114. blocks))))
  115. pages-to-blocks))))
  116. (defn- create-graph-db
  117. "Creates a sqlite-based db graph given a map of pages to blocks and returns a datascript db.
  118. Blocks in a map can only be top-level blocks with no referenced content"
  119. [dir db-name pages-to-blocks]
  120. (let [conn (sqlite-cli/open-db! dir db-name)
  121. frontend-blocks (create-frontend-blocks pages-to-blocks)
  122. _ (d/transact! conn frontend-blocks)]
  123. (gp-db/create-default-pages! conn)
  124. @conn))
  125. (defn- datoms->entity-maps
  126. "Returns entity maps for given :eavt datoms"
  127. [datoms]
  128. (->> datoms
  129. (reduce (fn [acc m]
  130. (update acc (:e m) assoc (:a m) (:v m)))
  131. {})
  132. vals))
  133. ;; This test compares the :eavt datoms from a file graph and a db graph to ensure
  134. ;; their differences are minimal and remain constant
  135. (deftest file-and-db-graphs-have-expected-differences
  136. (let [graph-dir "tmp/file-and-db-graph"
  137. ;; pages and their blocks which are being tested
  138. pages-to-blocks
  139. {{:block/name "page1" :block/type ["page"]}
  140. [{:block/content "block 1"} {:block/content "block 2"}]
  141. {:block/name "jul 20th, 2023" :block/type #{"journal"} :block/journal-day 20230720}
  142. [{:block/content "b1"} {:block/content "b2"}]}
  143. file-db (create-file-db graph-dir pages-to-blocks)
  144. graph-db (create-graph-db "tmp" "file-and-db-graph" pages-to-blocks)
  145. ;; Only test meaningful differences like content, name and set of block attributes.
  146. ;; Most attribute values won't be the same as they are random e.g. timestamps and db ids.
  147. file-ents (->> (d/datoms file-db :eavt)
  148. datoms->entity-maps
  149. (map #(assoc (or (not-empty (select-keys % [:block/content :block/name]))
  150. %)
  151. :attributes (disj (set (keys %)) :block/file :block/format)))
  152. set)
  153. db-ents (->> (d/datoms graph-db :eavt)
  154. datoms->entity-maps
  155. (map #(assoc (or (not-empty (select-keys % [:block/content :block/name]))
  156. %)
  157. :attributes (cond-> (disj (set (keys %))
  158. ;; Don't compare :block/format as db graphs
  159. ;; are purposely different
  160. :block/format)
  161. (seq (:block/content %))
  162. (set/difference #{:block/created-at :block/updated-at}))))
  163. set)]
  164. (println "Datom counts for file and db graphs are" (count (d/datoms file-db :eavt)) "and" (count (d/datoms graph-db :eavt)))
  165. (is (empty? (set/difference db-ents file-ents))
  166. "All the entities in a db graph are also in a file graph")
  167. (let [file-only-ents (set/difference file-ents db-ents)]
  168. (is (= (count file-only-ents) (count pages-to-blocks))
  169. "There is an additional file entity for every page")
  170. (is (every? :file/path file-only-ents)
  171. "The only entities in a file graph (and not in a db graph) are file based ones"))))