mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 17:40:39 +00:00
A read-only workspace (saves failing, a user who cannot edit, a version preview) only hid the editing tools: plugins, the MCP among them, still changed shapes, and those edits were never saved. Every plugin write now throws a read-only error, whatever the throwValidationErrors flag, the same way a write to a removed shape does. `check-permission` with a write permission runs the new `check-writable`, which covers most writers. Writers without a permission check (most context methods such as create*, group, align or flatten, the z-order methods, applyTypography on a text range, removing a ruler guide) call it themselves, and shape setters get it through `check-shape-writable`. Reads, comments and undo blocks keep working, so a block opened before a lock still closes. The MCP instructions tell the agent what the read-only error means.
94 lines
3.4 KiB
Clojure
94 lines
3.4 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
|
|
|
(ns frontend-tests.plugins.read-only-test
|
|
"Plugin writes in a read-only workspace."
|
|
(:require
|
|
[app.common.test-helpers.files :as cthf]
|
|
[app.main.data.helpers :as dsh]
|
|
[app.main.store :as st]
|
|
[app.plugins.api :as api]
|
|
[app.plugins.register :as r]
|
|
[cljs.test :as t :include-macros true]
|
|
[frontend-tests.helpers.state :as ths]
|
|
[frontend-tests.helpers.wasm :as thw]))
|
|
|
|
;; Its validation flag is off, so only errors that always throw reach it
|
|
(def ^:private plugin-id "00000000-0000-0000-0000-000000000000")
|
|
|
|
(defn- set-read-only
|
|
[store read-only?]
|
|
(swap! store assoc-in [:workspace-global :read-only?] read-only?))
|
|
|
|
(defn- shape-count
|
|
[store]
|
|
(count (dsh/lookup-page-objects @store)))
|
|
|
|
(defn- with-context
|
|
"Calls `f` with a store holding a sample file and a plugin context on it."
|
|
[f]
|
|
(thw/with-wasm-mocks*
|
|
(fn []
|
|
(let [store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))]
|
|
(set! st/state store)
|
|
(f store (api/create-context plugin-id))))))
|
|
|
|
(t/deftest writes-throw-in-a-read-only-workspace
|
|
(with-context
|
|
(fn [store ^js context]
|
|
(let [^js board (.createBoard context)]
|
|
(set-read-only store true)
|
|
|
|
(t/testing "a shape setter"
|
|
(t/is (thrown-with-msg? js/Error #"read-only" (set! (.-name board) "Renamed")))
|
|
(t/is (not= "Renamed" (.-name board))))
|
|
|
|
(t/testing "a shape method"
|
|
(t/is (thrown-with-msg? js/Error #"read-only" (.bringToFront board))))
|
|
|
|
(t/testing "a context method without a permission check"
|
|
(let [before (shape-count store)]
|
|
(t/is (thrown-with-msg? js/Error #"read-only" (.createRectangle context)))
|
|
(t/is (= before (shape-count store)))))
|
|
|
|
(t/testing "a context method with a permission check"
|
|
(t/is (thrown-with-msg? js/Error #"read-only" (.createPage (.-currentFile context)))))))))
|
|
|
|
(t/deftest writes-work-again-once-the-workspace-is-editable
|
|
(with-context
|
|
(fn [store ^js context]
|
|
(let [^js board (.createBoard context)]
|
|
(set-read-only store true)
|
|
(set-read-only store false)
|
|
(set! (.-name board) "Renamed")
|
|
(t/is (= "Renamed" (.-name board)))))))
|
|
|
|
(t/deftest a-save-failure-lock-says-why-the-workspace-is-read-only
|
|
(with-context
|
|
(fn [store ^js context]
|
|
(swap! store update :workspace-global assoc
|
|
:read-only? true
|
|
:persistence-lock {:read-only? false})
|
|
(t/is (thrown-with-msg? js/Error #"cannot be saved" (.createBoard context))))))
|
|
|
|
(t/deftest reads-and-undo-blocks-work-in-a-read-only-workspace
|
|
(with-context
|
|
(fn [store ^js context]
|
|
(let [^js board (.createBoard context)]
|
|
(set-read-only store true)
|
|
(t/is (string? (.-name board)))
|
|
(let [block (.undoBlockBegin (.-history context))]
|
|
(t/is (some? block))
|
|
(t/is (nil? (.undoBlockFinish (.-history context) block))))))))
|
|
|
|
(t/deftest read-only-keeps-permission-checks-of-reads
|
|
(with-context
|
|
(fn [store _]
|
|
(set-read-only store true)
|
|
(t/is (true? (r/check-permission plugin-id "content:read")))
|
|
(t/is (true? (r/has-permission? plugin-id "content:write")))
|
|
(t/is (thrown? js/Error (r/check-permission plugin-id "library:write"))))))
|