exporter_test.cljs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. (ns ^:node-only logseq.graph-parser.exporter-test
  2. (:require [cljs.test :refer [testing is]]
  3. [logseq.graph-parser.test.helper :as test-helper :include-macros true :refer [deftest-async]]
  4. [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
  5. [datascript.core :as d]
  6. [clojure.string :as string]
  7. [clojure.set :as set]
  8. ["path" :as node-path]
  9. ["fs" :as fs]
  10. [logseq.common.graph :as common-graph]
  11. [promesa.core :as p]
  12. [logseq.db.frontend.schema :as db-schema]
  13. [logseq.db.frontend.validate :as db-validate]
  14. [logseq.db.sqlite.create-graph :as sqlite-create-graph]
  15. [logseq.graph-parser.exporter :as gp-exporter]
  16. [logseq.db.frontend.malli-schema :as db-malli-schema]
  17. [logseq.db.frontend.property :as db-property]
  18. [logseq.db.frontend.property.type :as db-property-type]
  19. [logseq.common.config :as common-config]
  20. [logseq.db :as ldb]
  21. [logseq.outliner.db-pipeline :as db-pipeline]))
  22. ;; Helpers
  23. ;; =======
  24. ;; some have been copied from db-import script
  25. (defn- find-block-by-content [db content]
  26. (if (instance? js/RegExp content)
  27. (->> content
  28. (d/q '[:find [(pull ?b [*]) ...]
  29. :in $ ?pattern
  30. :where [?b :block/title ?content]
  31. [(missing? $ ?b :block/type)]
  32. [(re-find ?pattern ?content)]]
  33. db)
  34. first)
  35. (->> content
  36. (d/q '[:find [(pull ?b [*]) ...]
  37. :in $ ?content
  38. :where [?b :block/title ?content] [(missing? $ ?b :block/type)]]
  39. db)
  40. first)))
  41. (defn- find-page-by-name [db name]
  42. (->> name
  43. (d/q '[:find [(pull ?b [*]) ...]
  44. :in $ ?name
  45. :where [?b :block/title ?name]]
  46. db)
  47. first))
  48. (defn- build-graph-files
  49. "Given a file graph directory, return all files including assets and adds relative paths
  50. on ::rpath since paths are absolute by default and exporter needs relative paths for
  51. some operations"
  52. [dir*]
  53. (let [dir (node-path/resolve dir*)]
  54. (->> (common-graph/get-files dir)
  55. (concat (when (fs/existsSync (node-path/join dir* "assets"))
  56. (common-graph/readdir (node-path/join dir* "assets"))))
  57. (mapv #(hash-map :path %
  58. ::rpath (node-path/relative dir* %))))))
  59. (defn- <read-file
  60. [file]
  61. (p/let [s (fs/readFileSync (:path file))]
  62. (str s)))
  63. (defn- notify-user [m]
  64. (println (:msg m))
  65. (when (:ex-data m)
  66. (println "Ex-data:" (pr-str (dissoc (:ex-data m) :error)))
  67. (println "Stacktrace:")
  68. (if-let [stack (some-> (get-in m [:ex-data :error]) ex-data :sci.impl/callstack deref)]
  69. (println (string/join
  70. "\n"
  71. (map
  72. #(str (:file %)
  73. (when (:line %) (str ":" (:line %)))
  74. (when (:sci.impl/f-meta %)
  75. (str " calls #'" (get-in % [:sci.impl/f-meta :ns]) "/" (get-in % [:sci.impl/f-meta :name]))))
  76. (reverse stack))))
  77. (println (some-> (get-in m [:ex-data :error]) .-stack))))
  78. (when (= :error (:level m))
  79. (js/process.exit 1)))
  80. (def default-export-options
  81. {;; common options
  82. :rpath-key ::rpath
  83. :notify-user notify-user
  84. :<read-file <read-file
  85. ;; :set-ui-state prn
  86. ;; config file options
  87. ;; TODO: Add actual default
  88. :default-config {}})
  89. ;; Copied from db-import script and tweaked for an in-memory import
  90. (defn- import-file-graph-to-db
  91. "Import a file graph dir just like UI does. However, unlike the UI the
  92. exporter receives file maps containing keys :path and ::rpath since :path
  93. are full paths"
  94. [file-graph-dir conn {:keys [assets] :as options}]
  95. (let [*files (build-graph-files file-graph-dir)
  96. config-file (first (filter #(string/ends-with? (:path %) "logseq/config.edn") *files))
  97. _ (assert config-file "No 'logseq/config.edn' found for file graph dir")
  98. options' (-> (merge default-export-options
  99. options
  100. ;; asset file options
  101. {:<copy-asset #(swap! assets conj %)})
  102. (dissoc :assets))]
  103. (gp-exporter/export-file-graph conn conn config-file *files options')))
  104. (defn- import-files-to-db
  105. "Import specific doc files for dev purposes"
  106. [files conn options]
  107. (p/let [doc-options (gp-exporter/build-doc-options {:macros {}} (merge default-export-options options))
  108. files' (mapv #(hash-map :path %) files)
  109. _ (gp-exporter/export-doc-files conn files' <read-file doc-options)]
  110. {:import-state (:import-state doc-options)}))
  111. (defn- readable-properties
  112. [db query-ent]
  113. (->> (db-property/properties query-ent)
  114. (map (fn [[k v]]
  115. (if (boolean? v)
  116. [k v]
  117. [k
  118. (if-let [built-in-type (get-in db-property/built-in-properties [k :schema :type])]
  119. (if (= :block/tags k)
  120. (mapv #(:db/ident (d/entity db (:db/id %))) v)
  121. (if (db-property-type/all-ref-property-types built-in-type)
  122. (db-property/ref->property-value-contents db v)
  123. v))
  124. (db-property/ref->property-value-contents db v))])))
  125. (into {})))
  126. ;; Tests
  127. ;; =====
  128. (deftest-async ^:integration export-docs-graph
  129. (p/let [file-graph-dir "test/resources/docs-0.10.9"
  130. _ (docs-graph-helper/clone-docs-repo-if-not-exists file-graph-dir "v0.10.9")
  131. conn (d/create-conn db-schema/schema-for-db-based-graph)
  132. _ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))
  133. assets (atom [])
  134. {:keys [import-state]}
  135. (import-file-graph-to-db file-graph-dir conn {:assets assets})]
  136. (is (empty? (map :entity (:errors (db-validate/validate-db! @conn))))
  137. "Created graph has no validation errors")
  138. (is (= 0 (count @(:ignored-properties import-state))) "No ignored properties")))
  139. (deftest-async export-basic-graph
  140. ;; This graph will contain basic examples of different features to import
  141. (p/let [file-graph-dir "test/resources/exporter-test-graph"
  142. conn (d/create-conn db-schema/schema-for-db-based-graph)
  143. _ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))
  144. ;; Simulate frontend path-refs being calculated
  145. _ (db-pipeline/add-listener conn)
  146. assets (atom [])
  147. {:keys [import-state]} (import-file-graph-to-db file-graph-dir conn {:assets assets})]
  148. (testing "whole graph"
  149. (is (empty? (map :entity (:errors (db-validate/validate-db! @conn))))
  150. "Created graph has no validation errors")
  151. ;; Counts
  152. ;; Includes journals as property values e.g. :logseq.task/deadline
  153. (is (= 18 (count (d/q '[:find ?b :where [?b :block/type "journal"]] @conn))))
  154. (is (= 18 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Journal]] @conn))))
  155. (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Task]] @conn))))
  156. (is (= 1 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Query]] @conn))))
  157. ;; Don't count pages like url.md that have properties but no content
  158. (is (= 8
  159. (count (->> (d/q '[:find [(pull ?b [:block/title :block/type]) ...]
  160. :where [?b :block/title] [_ :block/page ?b]] @conn)
  161. (filter #(= "page" (:block/type %))))))
  162. "Correct number of pages with block content")
  163. (is (= 4 (count (d/datoms @conn :avet :block/type "whiteboard"))))
  164. (is (= 1 (count @(:ignored-properties import-state))) ":filters should be the only ignored property")
  165. (is (= 1 (count @assets))))
  166. (testing "logseq files"
  167. (is (= ".foo {}\n"
  168. (ffirst (d/q '[:find ?content :where [?b :file/path "logseq/custom.css"] [?b :file/content ?content]] @conn))))
  169. (is (= "logseq.api.show_msg('hello good sir!');\n"
  170. (ffirst (d/q '[:find ?content :where [?b :file/path "logseq/custom.js"] [?b :file/content ?content]] @conn)))))
  171. (testing "favorites"
  172. (is (= #{"Interstellar" "some page"}
  173. (->>
  174. (ldb/get-page-blocks @conn
  175. (:db/id (ldb/get-page @conn common-config/favorites-page-name))
  176. {:pull-keys '[* {:block/link [:block/title]}]})
  177. (map #(get-in % [:block/link :block/title]))
  178. set))))
  179. (testing "user properties"
  180. (is (= 18
  181. (->> @conn
  182. (d/q '[:find [(pull ?b [:db/ident]) ...]
  183. :where [?b :block/type "property"]])
  184. (remove #(db-malli-schema/internal-ident? (:db/ident %)))
  185. count))
  186. "Correct number of user properties")
  187. (is (= #{{:db/ident :user.property/prop-bool :block/schema {:type :checkbox}}
  188. {:db/ident :user.property/prop-string :block/schema {:type :default}}
  189. {:db/ident :user.property/prop-num :block/schema {:type :number}}
  190. {:db/ident :user.property/sameas :block/schema {:type :url}}
  191. {:db/ident :user.property/rangeincludes :block/schema {:type :node}}
  192. {:db/ident :user.property/startedat :block/schema {:type :date}}}
  193. (->> @conn
  194. (d/q '[:find [(pull ?b [:db/ident :block/schema]) ...]
  195. :where [?b :block/type "property"]])
  196. (filter #(contains? #{:prop-bool :prop-string :prop-num :rangeincludes :sameas :startedat}
  197. (keyword (name (:db/ident %)))))
  198. set))
  199. "Main property types have correct inferred :type")
  200. (is (= :default
  201. (get-in (d/entity @conn :user.property/description) [:block/schema :type]))
  202. "Property value consisting of text and refs is inferred as :default")
  203. (is (= :url
  204. (get-in (d/entity @conn :user.property/url) [:block/schema :type]))
  205. "Property value with a macro correctly inferred as :url")
  206. (is (= {:user.property/prop-bool true
  207. :user.property/prop-num 5
  208. :user.property/prop-string "woot"}
  209. (update-vals (db-property/properties (find-block-by-content @conn "b1"))
  210. (fn [v] (if (map? v) (db-property/ref->property-value-content @conn v) v))))
  211. "Basic block has correct properties")
  212. (is (= #{"prop-num" "prop-string" "prop-bool"}
  213. (->> (d/entity @conn (:db/id (find-block-by-content @conn "b1")))
  214. :block/refs
  215. (map :block/title)
  216. set))
  217. "Block with properties has correct refs")
  218. (is (= {:user.property/prop-num2 10}
  219. (readable-properties @conn (find-page-by-name @conn "new page")))
  220. "New page has correct properties")
  221. (is (= {:user.property/prop-bool true
  222. :user.property/prop-num 5
  223. :user.property/prop-string "yeehaw"}
  224. (readable-properties @conn (find-page-by-name @conn "some page")))
  225. "Existing page has correct properties")
  226. (is (= {:user.property/rating 5.5}
  227. (readable-properties @conn (find-block-by-content @conn ":rating float")))
  228. "Block with float property imports as a float"))
  229. (testing "built-in properties"
  230. (is (= [(:db/id (find-block-by-content @conn "original block"))]
  231. (mapv :db/id (:block/refs (find-block-by-content @conn #"ref to"))))
  232. "block with a block-ref has correct :block/refs")
  233. (let [b (find-block-by-content @conn #"MEETING TITLE")]
  234. (is (= {}
  235. (and b (readable-properties @conn b)))
  236. ":template properties are ignored to not invalidate its property types"))
  237. (is (= {:logseq.task/deadline "Nov 26th, 2022"}
  238. (readable-properties @conn (find-block-by-content @conn "only deadline")))
  239. "deadline block has correct journal as property value")
  240. (is (= {:logseq.task/deadline "Nov 25th, 2022"}
  241. (readable-properties @conn (find-block-by-content @conn "only scheduled")))
  242. "scheduled block converted to correct deadline")
  243. (is (= {:logseq.task/priority "High"}
  244. (readable-properties @conn (find-block-by-content @conn "high priority")))
  245. "priority block has correct property")
  246. (is (= {:logseq.task/status "Doing" :logseq.task/priority "Medium" :block/tags [:logseq.class/Task]}
  247. (readable-properties @conn (find-block-by-content @conn "status test")))
  248. "status block has correct task properties and class")
  249. (is (= #{:logseq.task/status :block/tags}
  250. (set (keys (readable-properties @conn (find-block-by-content @conn "old todo block")))))
  251. "old task properties like 'todo' are ignored")
  252. (is (= {:logseq.property/order-list-type "number"}
  253. (readable-properties @conn (find-block-by-content @conn "list one")))
  254. "numered block has correct property")
  255. (is (= #{"gpt"}
  256. (:block/alias (readable-properties @conn (find-page-by-name @conn "chat-gpt")))))
  257. (is (= {:logseq.property.table/sorting [{:id :user.property/prop-num, :asc? false}]
  258. :logseq.property.view/type "Table View"
  259. :logseq.property.table/ordered-columns [:block/title :user.property/prop-string :user.property/prop-num]
  260. :block/tags [:logseq.class/Query]}
  261. (readable-properties @conn (find-block-by-content @conn "{{query (property :prop-string)}}")))
  262. "query block has correct query properties"))
  263. (testing "db attributes"
  264. (is (= true
  265. (:block/collapsed? (find-block-by-content @conn "collapsed block")))
  266. "Collapsed blocks are imported"))
  267. (testing "property :type changes"
  268. (is (= :node
  269. (get-in (d/entity @conn :user.property/finishedat) [:block/schema :type]))
  270. ":date property to :node value changes to :node")
  271. (is (= :node
  272. (get-in (d/entity @conn :user.property/participants) [:block/schema :type]))
  273. ":node property to :date value remains :node")
  274. (is (= :default
  275. (get-in (d/entity @conn :user.property/description) [:block/schema :type]))
  276. ":default property to :node (or any non :default value) remains :default")
  277. (is (= "[[Jakob]]"
  278. (:user.property/description (readable-properties @conn (find-block-by-content @conn #":default to :node"))))
  279. ":default to :node property saves :default property value default with full text")
  280. (testing "with changes to upstream/existing property value"
  281. (is (= :default
  282. (get-in (d/entity @conn :user.property/duration) [:block/schema :type]))
  283. ":number property to :default value changes to :default")
  284. (is (= "20"
  285. (:user.property/duration (readable-properties @conn (find-block-by-content @conn "existing :number to :default"))))
  286. "existing :number property value correctly saved as :default")
  287. (is (= {:block/schema {:type :default} :db/cardinality :db.cardinality/many}
  288. (select-keys (d/entity @conn :user.property/people) [:block/schema :db/cardinality]))
  289. ":node property to :default value changes to :default and keeps existing cardinality")
  290. (is (= #{"[[Jakob]] [[Gabriel]]"}
  291. (:user.property/people (readable-properties @conn (find-block-by-content @conn ":node people"))))
  292. "existing :node property value correctly saved as :default with full text")
  293. (is (= #{"[[Gabriel]] [[Jakob]]"}
  294. (:user.property/people (readable-properties @conn (find-block-by-content @conn #"pending block for :node"))))
  295. "pending :node property value correctly saved as :default with full text")))
  296. (testing "replacing refs in :block/title"
  297. (is (= 2
  298. (->> (find-block-by-content @conn #"replace with same start string")
  299. :block/title
  300. (re-seq #"\[\[~\^\S+\]\]")
  301. distinct
  302. count))
  303. "A block with ref names that start with same string has 2 distinct refs")
  304. (is (= 1
  305. (->> (find-block-by-content @conn #"replace case insensitive")
  306. :block/title
  307. (re-seq #"\[\[~\^\S+\]\]")
  308. distinct
  309. count))
  310. "A block with different case of same ref names has 1 distinct ref"))
  311. (testing "imported concepts can have names of new-built concepts"
  312. (is (= #{:logseq.property/description :user.property/description}
  313. (set (d/q '[:find [?ident ...] :where [?b :db/ident ?ident] [?b :block/name "description"]] @conn)))
  314. "user description property is separate from built-in one")
  315. (is (= #{"page" "class"}
  316. (set (d/q '[:find [?type ...] :where [?b :block/type ?type] [?b :block/name "task"]] @conn)))
  317. "user page is separate from built-in class"))
  318. (testing "multiline blocks"
  319. (is (= "|markdown| table|\n|some|thing|" (:block/title (find-block-by-content @conn #"markdown.*table"))))
  320. (is (= "multiline block\na 2nd\nand a 3rd" (:block/title (find-block-by-content @conn #"multiline block"))))
  321. (is (= "logbook block" (:block/title (find-block-by-content @conn #"logbook block"))))
  322. (is (re-find #"(?s)^Text before\n#\+BEGIN_QUERY.*END_QUERY\nText after$"
  323. (:block/title (find-block-by-content @conn #":title \"tasks")))))
  324. (testing "block refs and path-refs"
  325. (let [block (find-block-by-content @conn "old todo block")]
  326. (is (set/subset?
  327. #{:logseq.task/status :logseq.class/Task}
  328. (->> block
  329. :block/path-refs
  330. (map #(:db/ident (d/entity @conn (:db/id %))))
  331. set))
  332. "Correct :block/refs")
  333. (is (set/subset?
  334. #{:logseq.task/status :logseq.class/Task}
  335. (->> block
  336. :block/path-refs
  337. (map #(:db/ident (d/entity @conn (:db/id %))))
  338. set))
  339. "Correct :block/path-refs")))
  340. (testing "whiteboards"
  341. (let [block-with-props (find-block-by-content @conn #"block with props")]
  342. (is (= {:user.property/prop-num 10}
  343. (readable-properties @conn block-with-props)))
  344. (is (= "block with props" (:block/title block-with-props)))))
  345. (testing "tags without tag options"
  346. (let [block (find-block-by-content @conn #"Inception")
  347. tag-page (find-page-by-name @conn "Movie")
  348. tagged-page (find-page-by-name @conn "Interstellar")]
  349. (is (string/starts-with? (str (:block/title block)) "Inception [[")
  350. "tagged block tag converts tag to page ref")
  351. (is (= [(:db/id tag-page)] (map :db/id (:block/refs block)))
  352. "tagged block has correct refs")
  353. (is (and tag-page (not (ldb/class? tag-page)))
  354. "tag page is not a class")
  355. (is (= {:logseq.property/page-tags #{"Movie"}}
  356. (readable-properties @conn tagged-page))
  357. "tagged page has existing page imported as a tag to page-tags")
  358. (is (= #{"LargeLanguageModel" "fun" "ai"}
  359. (:logseq.property/page-tags (readable-properties @conn (find-page-by-name @conn "chat-gpt"))))
  360. "tagged page has new page and other pages marked with '#' and '[[]]` imported as tags to page-tags")))))
  361. (deftest-async export-files-with-tag-classes-option
  362. (p/let [file-graph-dir "test/resources/exporter-test-graph"
  363. files (mapv #(node-path/join file-graph-dir %) ["journals/2024_02_07.md" "pages/Interstellar.md"])
  364. conn (d/create-conn db-schema/schema-for-db-based-graph)
  365. _ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))
  366. _ (import-files-to-db files conn {:tag-classes ["movie"]})]
  367. (is (empty? (map :entity (:errors (db-validate/validate-db! @conn))))
  368. "Created graph has no validation errors")
  369. (let [block (find-block-by-content @conn #"Inception")
  370. tag-page (find-page-by-name @conn "Movie")
  371. another-tag-page (find-page-by-name @conn "p0")]
  372. (is (= (:block/title block) "Inception")
  373. "tagged block with configured tag strips tag from content")
  374. (is (= [:user.class/Movie]
  375. (:block/tags (readable-properties @conn block)))
  376. "tagged block has configured tag imported as a class")
  377. (is (= "class" (:block/type tag-page))
  378. "configured tag page in :tag-classes is a class")
  379. (is (and another-tag-page (not (ldb/class? another-tag-page)))
  380. "unconfigured tag page is not a class")
  381. (is (= {:block/tags [:user.class/Movie]}
  382. (readable-properties @conn (find-page-by-name @conn "Interstellar")))
  383. "tagged page has configured tag imported as a class"))))
  384. (deftest-async export-files-with-property-classes-option
  385. (p/let [file-graph-dir "test/resources/exporter-test-graph"
  386. files (mapv #(node-path/join file-graph-dir %) ["journals/2024_02_23.md" "pages/url.md"])
  387. conn (d/create-conn db-schema/schema-for-db-based-graph)
  388. _ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))
  389. _ (import-files-to-db files conn {:property-classes ["type"]})
  390. _ (@#'gp-exporter/export-class-properties conn conn)]
  391. (is (empty? (map :entity (:errors (db-validate/validate-db! @conn))))
  392. "Created graph has no validation errors")
  393. (is (= #{:user.class/Property :user.class/Movie}
  394. (->> @conn
  395. (d/q '[:find [?ident ...]
  396. :where [?b :block/type "class"] [?b :db/ident ?ident] (not [?b :logseq.property/built-in?])])
  397. set))
  398. "All classes are correctly defined by :type")
  399. (is (= #{:user.property/url :user.property/sameas :user.property/rangeincludes}
  400. (->> (d/entity @conn :user.class/Property)
  401. :logseq.property.class/properties
  402. (map :db/ident)
  403. set))
  404. "Properties are correctly inferred for a class")
  405. (let [block (find-block-by-content @conn #"The Creator")
  406. tag-page (find-page-by-name @conn "Movie")]
  407. (is (= (:block/title block) "The Creator")
  408. "tagged block with configured tag strips tag from content")
  409. (is (= [:user.class/Movie]
  410. (:block/tags (readable-properties @conn block)))
  411. "tagged block has configured tag imported as a class")
  412. (is (= (:user.property/testtagclass block) (:block/tags block))
  413. "tagged block can have another property that references the same class it is tagged with,
  414. without creating a duplicate class")
  415. (is (= "class" (:block/type tag-page))
  416. "configured tag page derived from :property-classes is a class")
  417. (is (nil? (find-page-by-name @conn "type"))
  418. "No page exists for configured property")
  419. (is (= [:user.class/Property]
  420. (:block/tags (readable-properties @conn (find-page-by-name @conn "url"))))
  421. "tagged page has configured tag imported as a class"))))
  422. (deftest-async export-files-with-ignored-properties
  423. (p/let [file-graph-dir "test/resources/exporter-test-graph"
  424. files (mapv #(node-path/join file-graph-dir %) ["ignored/icon-page.md"])
  425. conn (d/create-conn db-schema/schema-for-db-based-graph)
  426. _ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))
  427. {:keys [import-state]} (import-files-to-db files conn {})]
  428. (is (= 2
  429. (count (filter #(= :icon (:property %)) @(:ignored-properties import-state))))
  430. "icon properties are visibly ignored in order to not fail import")))
  431. (deftest-async export-files-with-property-parent-classes-option
  432. (p/let [file-graph-dir "test/resources/exporter-test-graph"
  433. files (mapv #(node-path/join file-graph-dir %) ["pages/CreativeWork.md" "pages/Movie.md" "pages/type.md"])
  434. conn (d/create-conn db-schema/schema-for-db-based-graph)
  435. _ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))
  436. _ (import-files-to-db files conn {:property-parent-classes ["parent"]})]
  437. (is (empty? (map :entity (:errors (db-validate/validate-db! @conn))))
  438. "Created graph has no validation errors")
  439. (is (= #{:user.class/Movie :user.class/CreativeWork :user.class/Thing}
  440. (->> @conn
  441. (d/q '[:find [?ident ...]
  442. :where [?b :block/type "class"] [?b :db/ident ?ident] (not [?b :logseq.property/built-in?])])
  443. set))
  444. "All classes are correctly defined by :type")
  445. (is (= "CreativeWork" (get-in (d/entity @conn :user.class/Movie) [:logseq.property/parent :block/title]))
  446. "Existing page correctly set as class parent")
  447. (is (= "Thing" (get-in (d/entity @conn :user.class/CreativeWork) [:logseq.property/parent :block/title]))
  448. "New page correctly set as class parent")))