From 8c71f2cacfac63821c4c010c5937f5481df145b8 Mon Sep 17 00:00:00 2001 From: rcmerci Date: Fri, 22 May 2026 21:45:32 +0800 Subject: [PATCH 1/2] fix(cli): handle inline block properties --- cli-e2e/spec/non_sync_cases.edn | 38 ++++++ src/main/logseq/cli/command/add.cljs | 99 ++++++++++++++- src/test/logseq/cli/command/add_test.cljs | 140 ++++++++++++++++++++++ 3 files changed, 275 insertions(+), 2 deletions(-) diff --git a/cli-e2e/spec/non_sync_cases.edn b/cli-e2e/spec/non_sync_cases.edn index b42731d51f..f3e73c3c95 100644 --- a/cli-e2e/spec/non_sync_cases.edn +++ b/cli-e2e/spec/non_sync_cases.edn @@ -776,6 +776,44 @@ PY" :upsert ["--blocks-file" "--target-id" "--pos"]}}, :tags [:upsert], :extends :non-sync/graph-json-env} + {:id "block-upsert-blocks-file-inline-property-name-json", + :setup + ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page Home >/dev/null" + "{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert property --graph {{graph-arg}} --name 'Correct Answer' --type default --cardinality one --public true >/dev/null" + "printf '%s' '[{:block/title \"Question block\" \"Correct Answer\" \"answer\"}]' > {{export-path-arg}}"], + :cmds + ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page Home --blocks-file {{export-path-arg}} >/dev/null" + "{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output human show --graph {{graph-arg}} --page Home"], + :expect + {:exit 0, + :stdout-contains ["Question block" "Correct Answer:" "answer"]}, + :covers + {:commands ["upsert block" "upsert property" "show"], + :options + {:global ["--config" "--graph" "--root-dir" "--output"], + :upsert ["--blocks-file" "--target-page" "--name" "--type" "--cardinality" "--public"], + :show ["--page"]}}, + :tags [:upsert :show], + :extends :non-sync/graph-json-env} + {:id "block-upsert-blocks-inline-properties-per-block-json", + :setup + ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page Home >/dev/null" + "{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert property --graph {{graph-arg}} --name 'Inline Note' --type default --cardinality one --public true >/dev/null"], + :cmds + ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page Home --blocks '[{:block/title \"First inline\" \"Inline Note\" \"First note\"} {:block/title \"Second inline\" \"Inline Note\" \"Second note\"}]' >/dev/null" + "first_id=\"$({{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json query --graph {{graph-arg}} --query '[:find ?e . :where [?e :block/title \"First inline\"]]' | python3 -c 'import sys,json; print(json.load(sys.stdin)[\"data\"][\"result\"])')\"; second_id=\"$({{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json query --graph {{graph-arg}} --query '[:find ?e . :where [?e :block/title \"Second inline\"]]' | python3 -c 'import sys,json; print(json.load(sys.stdin)[\"data\"][\"result\"])')\"; first_show=\"$({{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output human show --graph {{graph-arg}} --id \"$first_id\")\"; second_show=\"$({{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output human show --graph {{graph-arg}} --id \"$second_id\")\"; printf '%s\n---\n%s\n' \"$first_show\" \"$second_show\"; printf '%s' \"$first_show\" | grep 'Inline Note: First note' >/dev/null; ! printf '%s' \"$first_show\" | grep 'Second note' >/dev/null; printf '%s' \"$second_show\" | grep 'Inline Note: Second note' >/dev/null; ! printf '%s' \"$second_show\" | grep 'First note' >/dev/null"], + :expect + {:exit 0, + :stdout-contains ["First inline" "Inline Note: First note" "Second inline" "Inline Note: Second note"]}, + :covers + {:commands ["upsert block" "upsert property" "query" "show"], + :options + {:global ["--config" "--graph" "--root-dir" "--output"], + :upsert ["--blocks" "--target-page" "--name" "--type" "--cardinality" "--public"], + :query ["--query"], + :show ["--id"]}}, + :tags [:upsert :show], + :extends :non-sync/graph-json-env} {:id "block-upsert-target-uuid-json", :setup ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page Home >/dev/null" diff --git a/src/main/logseq/cli/command/add.cljs b/src/main/logseq/cli/command/add.cljs index ad2027e7a9..fb959c2f6e 100644 --- a/src/main/logseq/cli/command/add.cljs +++ b/src/main/logseq/cli/command/add.cljs @@ -168,6 +168,74 @@ block))) blocks)) +(def ^:private inline-property-key-namespace-prefixes + ["logseq.property" "user.property" "plugin.property"]) + +(defn- inline-property-key-namespace? + [key-namespace] + (boolean + (and key-namespace + (some (fn [prefix] + (or (= key-namespace prefix) + (string/starts-with? key-namespace (str prefix ".")))) + inline-property-key-namespace-prefixes)))) + +(defn- public-built-in-property-key? + [k] + (if-let [property (get db-property/built-in-properties k)] + (true? (get-in property [:schema :public?])) + true)) + +(defn- inline-property-key? + [k] + (cond + (string? k) + (seq (string/trim k)) + + (qualified-keyword? k) + (and (inline-property-key-namespace? (namespace k)) + (public-built-in-property-key? k)) + + :else + false)) + +(defn- split-inline-property-attrs + [block] + (reduce-kv (fn [acc k v] + (if (inline-property-key? k) + (update acc :properties assoc k v) + (update acc :block assoc k v))) + {:block {} + :properties {}} + block)) + +(defn- extract-inline-properties + [blocks] + (letfn [(walk-block [block] + (let [{block-without-inline-properties :block + inline-properties :properties} (split-inline-property-attrs block) + children (:block/children block-without-inline-properties) + walked-children (when (seq children) + (mapv walk-block children)) + block' (if (seq walked-children) + (assoc block-without-inline-properties :block/children (mapv :block walked-children)) + block-without-inline-properties) + block-uuid (:block/uuid block')] + (when (and (seq inline-properties) (nil? block-uuid)) + (throw (ex-info "block uuid is required for inline properties" + {:code :missing-block-uuid-for-inline-properties + :properties (keys inline-properties)}))) + {:block block' + :property-assignments (cond-> [] + (seq inline-properties) + (conj {:block-uuid block-uuid + :properties inline-properties}) + (seq walked-children) + (into (mapcat :property-assignments walked-children)))}))] + (let [walked (mapv walk-block blocks)] + {:blocks (mapv :block walked) + :property-assignments (vec (mapcat :property-assignments walked))}))) + (defn- normalize-created-ids [ids] (->> ids @@ -1063,6 +1131,27 @@ properties))] (vec resolved-entries))))) +(defn- resolve-inline-property-assignments + [config repo property-assignments] + (if (seq property-assignments) + (p/let [resolved (p/all + (map (fn [{:keys [block-uuid properties]}] + (p/let [resolved-properties (resolve-properties config repo properties + {:allow-non-built-in? true})] + {:block-uuid block-uuid + :properties resolved-properties})) + property-assignments))] + (vec resolved)) + (p/resolved nil))) + +(defn- inline-property-assignment-ops + [property-assignments] + (mapcat (fn [{:keys [block-uuid properties]}] + (map (fn [[property-ident value]] + [:batch-set-property [[block-uuid] property-ident value {}]]) + properties)) + property-assignments)) + (defn- resolve-add-target [config {:keys [repo target-id target-uuid target-page-name]}] (cond @@ -1174,7 +1263,11 @@ blocks (if (seq refs) (normalize-block-title-refs action-blocks refs) action-blocks) - blocks-for-insert (flatten-block-tree blocks) + {blocks-without-inline-properties :blocks + inline-property-assignments :property-assignments} (extract-inline-properties blocks) + inline-property-assignments (resolve-inline-property-assignments cfg (:repo action) + inline-property-assignments) + blocks-for-insert (flatten-block-tree blocks-without-inline-properties) status (:status action) tags (if (contains? action :resolved-tags) (:resolved-tags action) @@ -1220,7 +1313,9 @@ (and (seq properties) (seq block-uuids)) (into (map (fn [[k v]] [:batch-set-property [block-uuids k v {}]]) - properties))) + properties)) + (seq inline-property-assignments) + (into (inline-property-assignment-ops inline-property-assignments))) apply-result (transport/invoke cfg :thread-api/apply-outliner-ops [(:repo action) ops {}]) created-ids (resolve-created-block-ids cfg (:repo action) blocks-for-insert apply-result)] {:status :ok diff --git a/src/test/logseq/cli/command/add_test.cljs b/src/test/logseq/cli/command/add_test.cljs index 8f1af61f4b..da85c36e0d 100644 --- a/src/test/logseq/cli/command/add_test.cljs +++ b/src/test/logseq/cli/command/add_test.cljs @@ -4,6 +4,7 @@ [cljs.test :refer [async deftest is testing]] [clojure.string :as string] [logseq.cli.command.add :as add-command] + [logseq.cli.server :as cli-server] [logseq.common.util.date-time :as date-time-util] [logseq.cli.transport :as transport] [promesa.core :as p])) @@ -96,6 +97,58 @@ (is (empty? (:id-refs result))) (is (= ["3.14" "1e5"] (:page-refs result)))))) +(deftest test-extract-inline-properties-from-blocks + (testing "removes inline property attrs while preserving block uuids and children" + (let [root-uuid (uuid "00000000-0000-0000-0000-000000000101") + child-uuid (uuid "00000000-0000-0000-0000-000000000102") + result (#'add-command/extract-inline-properties + [{:block/title "Root" + :block/uuid root-uuid + :block/page [:block/name "Home"] + :block/tags [1] + :user.property/root-answer "root" + "Correct Answer" "named value" + :logseq.property/status :logseq.property/status.todo + :block/children [{:block/title "Child" + :block/uuid child-uuid + :user.property/child-answer "child"}]}])] + (is (= [{:block/title "Root" + :block/uuid root-uuid + :block/page [:block/name "Home"] + :block/tags [1] + :block/children [{:block/title "Child" + :block/uuid child-uuid}]}] + (:blocks result))) + (is (= [{:block-uuid root-uuid + :properties {:user.property/root-answer "root" + "Correct Answer" "named value" + :logseq.property/status :logseq.property/status.todo}} + {:block-uuid child-uuid + :properties {:user.property/child-answer "child"}}] + (:property-assignments result)))))) + +(deftest test-extract-inline-properties-keeps-non-property-attrs + (testing "only property namespace keywords and string property names are extracted" + (let [block-uuid (uuid "00000000-0000-0000-0000-000000000103") + result (#'add-command/extract-inline-properties + [{:block/title "Block" + :block/uuid block-uuid + :db/id 101 + :build/keep-uuid? true + :plugin/option "kept" + :logseq.property.asset/type "png" + :plugin.property._test_plugin/rating "5"}])] + (is (= [{:block/title "Block" + :block/uuid block-uuid + :db/id 101 + :build/keep-uuid? true + :plugin/option "kept" + :logseq.property.asset/type "png"}] + (:blocks result))) + (is (= [{:block-uuid block-uuid + :properties {:plugin.property._test_plugin/rating "5"}}] + (:property-assignments result)))))) + (def ^:private mock-transport-invoke (fn [_ method args] (case method @@ -302,3 +355,90 @@ (p/catch (fn [e] (is false (str "unexpected error: " e)))) (p/finally done)))) + +(deftest test-execute-add-block-applies-inline-properties-per-block + (async done + (let [target-uuid (uuid "00000000-0000-0000-0000-000000000200") + root-uuid (uuid "00000000-0000-0000-0000-000000000201") + child-uuid (uuid "00000000-0000-0000-0000-000000000202") + ops* (atom nil) + resolve-properties-calls* (atom [])] + (-> (p/with-redefs [cli-server/ensure-server! (fn [_ _] {:base-url "http://example"}) + add-command/resolve-tags (fn [_ _ _] (p/resolved nil)) + add-command/resolve-properties + (fn [_ _ properties & [opts]] + (swap! resolve-properties-calls* conj {:properties properties + :opts opts}) + (p/resolved properties)) + add-command/resolve-property-identifiers (fn [_ _ _ & _] (p/resolved nil)) + transport/invoke + (fn [_ method args] + (case method + :thread-api/pull + (let [[_ _ lookup] args] + (p/resolved + (cond + (= lookup 900) + {:db/id 900 :block/uuid target-uuid} + + (= lookup [:block/uuid root-uuid]) + {:db/id 901 :block/uuid root-uuid} + + (= lookup [:block/uuid child-uuid]) + {:db/id 902 :block/uuid child-uuid} + + :else {}))) + + :thread-api/apply-outliner-ops + (let [[_ ops _] args] + (reset! ops* ops) + (p/resolved {:result :ok})) + + (p/rejected (ex-info "unexpected invoke" {:method method :args args}))))] + (p/let [result (add-command/execute-add-block + {:type :add-block + :repo "demo" + :target-id 900 + :pos "last-child" + :blocks [{:block/title "Root" + :block/uuid root-uuid + :user.property/root-answer "root" + :block/children [{:block/title "Child" + :block/uuid child-uuid + :user.property/child-answer "child"}]}]} + {})] + (is (= :ok (:status result))) + (is (= [901 902] (get-in result [:data :result]))) + (let [ops @ops* + insert-op (first ops) + inserted-blocks (get-in insert-op [1 0])] + (is (= :insert-blocks (first insert-op))) + (is (= [{:block/title "Root" + :block/uuid root-uuid} + {:block/title "Child" + :block/uuid child-uuid + :block/parent [:block/uuid root-uuid]}] + inserted-blocks)) + (is (some #(= [:batch-set-property [[root-uuid] + :user.property/root-answer + "root" + {}]] + %) + ops)) + (is (some #(= [:batch-set-property [[child-uuid] + :user.property/child-answer + "child" + {}]] + %) + ops)) + (is (some #(= {:properties {:user.property/root-answer "root"} + :opts {:allow-non-built-in? true}} + %) + @resolve-properties-calls*)) + (is (some #(= {:properties {:user.property/child-answer "child"} + :opts {:allow-non-built-in? true}} + %) + @resolve-properties-calls*))))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally done))))) From f5d94ec993fada289fee1a684122b91b0fed9866 Mon Sep 17 00:00:00 2001 From: rcmerci Date: Fri, 22 May 2026 22:32:47 +0800 Subject: [PATCH 2/2] fix(cli): handle inline block properties atomically --- cli-e2e/spec/non_sync_cases.edn | 33 ++++++++++++ src/main/logseq/cli/command/add.cljs | 24 +++++---- src/test/logseq/cli/command/add_test.cljs | 66 +++++++++++++++++++++-- 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/cli-e2e/spec/non_sync_cases.edn b/cli-e2e/spec/non_sync_cases.edn index f3e73c3c95..1c2ca92c65 100644 --- a/cli-e2e/spec/non_sync_cases.edn +++ b/cli-e2e/spec/non_sync_cases.edn @@ -795,6 +795,20 @@ PY" :show ["--page"]}}, :tags [:upsert :show], :extends :non-sync/graph-json-env} + {:id "block-upsert-blocks-inline-property-missing-does-not-create-target-page-json", + :cmds + ["set +e; out=\"$({{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page NewPage --blocks '[{:block/title \"Q\" \"Missing Prop\" \"x\"}]' 2>&1)\"; code=$?; set -e; printf '%s\n' \"$out\"; test \"$code\" -ne 0; printf '%s' \"$out\" | python3 -c 'import sys,json; data=json.load(sys.stdin); assert data[\"status\"] == \"error\"; assert data[\"error\"][\"code\"] == \"property-not-found\"'; page=\"$({{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json query --graph {{graph-arg}} --query '[:find ?e . :where [?e :block/title \"NewPage\"]]')\"; printf '%s\n' \"$page\"; printf '%s' \"$page\" | python3 -c 'import sys,json; data=json.load(sys.stdin); assert data[\"data\"][\"result\"] is None'"], + :expect + {:exit 0, + :stdout-contains ["property-not-found"]}, + :covers + {:commands ["upsert block" "query"], + :options + {:global ["--config" "--graph" "--root-dir" "--output"], + :upsert ["--blocks" "--target-page"], + :query ["--query"]}}, + :tags [:upsert], + :extends :non-sync/graph-json-env} {:id "block-upsert-blocks-inline-properties-per-block-json", :setup ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page Home >/dev/null" @@ -814,6 +828,25 @@ PY" :show ["--id"]}}, :tags [:upsert :show], :extends :non-sync/graph-json-env} + {:id "block-upsert-blocks-inline-property-numeric-id-json", + :setup + ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page Home >/dev/null" + "{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert property --graph {{graph-arg}} --name 'Numeric Prop' --type default --cardinality one --public true >/dev/null"], + :cmds + ["prop_id=\"$({{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json query --graph {{graph-arg}} --query '[:find ?e . :where [?e :block/title \"Numeric Prop\"]]' | python3 -c 'import sys,json; print(json.load(sys.stdin)[\"data\"][\"result\"])')\"; {{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page Home --blocks \"[{:block/title \\\"Inline numeric\\\" $prop_id \\\"inline\\\"}]\" >/dev/null" + "{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output human show --graph {{graph-arg}} --page Home"], + :expect + {:exit 0, + :stdout-contains ["Inline numeric" "Numeric Prop: inline"]}, + :covers + {:commands ["upsert block" "upsert property" "query" "show"], + :options + {:global ["--config" "--graph" "--root-dir" "--output"], + :upsert ["--blocks" "--target-page" "--name" "--type" "--cardinality" "--public"], + :query ["--query"], + :show ["--page"]}}, + :tags [:upsert :show], + :extends :non-sync/graph-json-env} {:id "block-upsert-target-uuid-json", :setup ["{{cli}} --root-dir {{root-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page Home >/dev/null" diff --git a/src/main/logseq/cli/command/add.cljs b/src/main/logseq/cli/command/add.cljs index fb959c2f6e..7a24c5cad6 100644 --- a/src/main/logseq/cli/command/add.cljs +++ b/src/main/logseq/cli/command/add.cljs @@ -192,10 +192,16 @@ (string? k) (seq (string/trim k)) + (or (number? k) (uuid? k)) + true + (qualified-keyword? k) (and (inline-property-key-namespace? (namespace k)) (public-built-in-property-key? k)) + (keyword? k) + true + :else false)) @@ -1252,22 +1258,22 @@ (defn execute-add-block [action config] (-> (p/let [cfg (cli-server/ensure-server! config (:repo action)) - target-block-uuid (resolve-add-target cfg action) action-blocks (normalize-block-content-keys (:blocks action)) - ref-values (collect-page-refs action-blocks) + {blocks-without-inline-properties :blocks + inline-property-assignments :property-assignments} (extract-inline-properties action-blocks) + inline-property-assignments (resolve-inline-property-assignments cfg (:repo action) + inline-property-assignments) + target-block-uuid (resolve-add-target cfg action) + ref-values (collect-page-refs blocks-without-inline-properties) {:keys [uuid-refs page-refs id-refs]} (partition-ref-values ref-values) _ (ensure-block-refs-exist! cfg (:repo action) uuid-refs) page-refs' (or (resolve-page-ref-entities cfg (:repo action) page-refs) []) id-refs' (or (resolve-id-ref-entities cfg (:repo action) id-refs) []) refs (into page-refs' id-refs') blocks (if (seq refs) - (normalize-block-title-refs action-blocks refs) - action-blocks) - {blocks-without-inline-properties :blocks - inline-property-assignments :property-assignments} (extract-inline-properties blocks) - inline-property-assignments (resolve-inline-property-assignments cfg (:repo action) - inline-property-assignments) - blocks-for-insert (flatten-block-tree blocks-without-inline-properties) + (normalize-block-title-refs blocks-without-inline-properties refs) + blocks-without-inline-properties) + blocks-for-insert (flatten-block-tree blocks) status (:status action) tags (if (contains? action :resolved-tags) (:resolved-tags action) diff --git a/src/test/logseq/cli/command/add_test.cljs b/src/test/logseq/cli/command/add_test.cljs index da85c36e0d..33d63e5d4d 100644 --- a/src/test/logseq/cli/command/add_test.cljs +++ b/src/test/logseq/cli/command/add_test.cljs @@ -128,8 +128,9 @@ (:property-assignments result)))))) (deftest test-extract-inline-properties-keeps-non-property-attrs - (testing "only property namespace keywords and string property names are extracted" + (testing "property selectors are extracted without treating normal block attrs as properties" (let [block-uuid (uuid "00000000-0000-0000-0000-000000000103") + property-uuid (uuid "00000000-0000-0000-0000-000000000104") result (#'add-command/extract-inline-properties [{:block/title "Block" :block/uuid block-uuid @@ -137,7 +138,10 @@ :build/keep-uuid? true :plugin/option "kept" :logseq.property.asset/type "png" - :plugin.property._test_plugin/rating "5"}])] + :plugin.property._test_plugin/rating "5" + 801 "by id" + property-uuid "by uuid" + :plain-title "by keyword"}])] (is (= [{:block/title "Block" :block/uuid block-uuid :db/id 101 @@ -146,7 +150,10 @@ :logseq.property.asset/type "png"}] (:blocks result))) (is (= [{:block-uuid block-uuid - :properties {:plugin.property._test_plugin/rating "5"}}] + :properties {:plugin.property._test_plugin/rating "5" + 801 "by id" + property-uuid "by uuid" + :plain-title "by keyword"}}] (:property-assignments result)))))) (def ^:private mock-transport-invoke @@ -442,3 +449,56 @@ (p/catch (fn [e] (is false (str "unexpected error: " e)))) (p/finally done))))) + +(deftest test-execute-add-block-does-not-create-target-page-when-inline-property-resolution-fails + (async done + (let [created-page-uuid (uuid "00000000-0000-0000-0000-000000000203") + ops* (atom [])] + (-> (p/with-redefs [cli-server/ensure-server! (fn [_ _] {:base-url "http://example"}) + add-command/resolve-properties + (fn [_ _ properties & _] + (if (= {"Missing Prop" "x"} properties) + (p/rejected (ex-info "property not found: \"Missing Prop\"" + {:code :property-not-found + :property "Missing Prop"})) + (p/resolved properties))) + transport/invoke + (fn [_ method args] + (case method + :thread-api/q + (p/resolved []) + + :thread-api/pull + (let [[_ _ lookup] args] + (p/resolved + (if (= lookup [:block/name "newpage"]) + {:db/id 900 + :block/uuid created-page-uuid + :block/name "newpage" + :block/title "NewPage"} + {}))) + + :thread-api/apply-outliner-ops + (let [[_ ops _] args] + (swap! ops* conj ops) + (p/resolved {:result :ok})) + + (p/rejected (ex-info "unexpected invoke" {:method method :args args}))))] + (-> (add-command/execute-add-block + {:type :add-block + :repo "demo" + :target-page-name "NewPage" + :pos "last-child" + :blocks [{:block/title "Q" + :block/uuid (uuid "00000000-0000-0000-0000-000000000204") + "Missing Prop" "x"}]} + {}) + (p/then (fn [_] + (is false "expected inline property resolution error"))) + (p/catch (fn [e] + (is (= :property-not-found (-> e ex-data :code))) + (is (empty? @ops*) + "target page creation must not run before inline properties validate"))))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally done)))))