mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 06:02:32 -05:00
Merge remote-tracking branch 'penpot/develop' into token-studio-develop
This commit is contained in:
commit
230b271be3
72 changed files with 4793 additions and 3475 deletions
|
@ -102,6 +102,16 @@ jobs:
|
|||
yarn install
|
||||
yarn test
|
||||
|
||||
- run:
|
||||
name: "frontend integration tests"
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
yarn install
|
||||
yarn run compile
|
||||
clojure -M:dev:shadow-cljs compile main
|
||||
yarn playwright install --with-deps chromium
|
||||
yarn e2e:test
|
||||
|
||||
- run:
|
||||
name: "backend tests"
|
||||
working_directory: "./backend"
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -68,3 +68,7 @@
|
|||
clj-profiler/
|
||||
node_modules
|
||||
frontend/.storybook/preview-body.html
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
|
|
@ -744,6 +744,7 @@
|
|||
(map lookupf)
|
||||
(map mk-change))
|
||||
updated-shapes))))
|
||||
|
||||
(apply-changes-local)))))
|
||||
|
||||
(defn update-component
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns app.common.files.libraries-common-helpers
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn generate-add-component-changes
|
||||
[changes root objects file-id page-id components-v2]
|
||||
(let [name (:name root)
|
||||
[path name] (cfh/parse-path-name name)
|
||||
|
||||
[root-shape new-shapes updated-shapes]
|
||||
(if-not components-v2
|
||||
(ctn/make-component-shape root objects file-id components-v2)
|
||||
(ctn/convert-shape-in-component root objects file-id))
|
||||
|
||||
changes (-> changes
|
||||
(pcb/add-component (:id root-shape)
|
||||
path
|
||||
name
|
||||
new-shapes
|
||||
updated-shapes
|
||||
(:id root)
|
||||
page-id))]
|
||||
[root-shape changes]))
|
||||
|
||||
(defn generate-add-component
|
||||
"If there is exactly one id, and it's a frame (or a group in v1), and not already a component,
|
||||
use it as root. Otherwise, create a frame (v2) or group (v1) that contains all ids. Then, make a
|
||||
component with it, and link all shapes to their corresponding one in the component."
|
||||
[it shapes objects page-id file-id components-v2 prepare-create-group prepare-create-board]
|
||||
|
||||
(let [changes (pcb/empty-changes it page-id)
|
||||
shapes-count (count shapes)
|
||||
first-shape (first shapes)
|
||||
|
||||
from-singe-frame?
|
||||
(and (= 1 shapes-count)
|
||||
(cfh/frame-shape? first-shape))
|
||||
|
||||
[root changes old-root-ids]
|
||||
(if (and (= shapes-count 1)
|
||||
(or (and (cfh/group-shape? first-shape)
|
||||
(not components-v2))
|
||||
(cfh/frame-shape? first-shape))
|
||||
(not (ctk/instance-head? first-shape)))
|
||||
[first-shape
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects))
|
||||
(:shapes first-shape)]
|
||||
|
||||
(let [root-name (if (= 1 shapes-count)
|
||||
(:name first-shape)
|
||||
"Component 1")
|
||||
|
||||
shape-ids (into (d/ordered-set) (map :id) shapes)
|
||||
|
||||
[root changes]
|
||||
(if-not components-v2
|
||||
(prepare-create-group it ; These functions needs to be passed as argument
|
||||
objects ; to avoid a circular dependence
|
||||
page-id
|
||||
shapes
|
||||
root-name
|
||||
(not (ctk/instance-head? first-shape)))
|
||||
(prepare-create-board changes
|
||||
(uuid/next)
|
||||
(:parent-id first-shape)
|
||||
objects
|
||||
shape-ids
|
||||
nil
|
||||
root-name
|
||||
true))]
|
||||
|
||||
[root changes shape-ids]))
|
||||
|
||||
changes
|
||||
(cond-> changes
|
||||
(not from-singe-frame?)
|
||||
(pcb/update-shapes
|
||||
(:shapes root)
|
||||
(fn [shape]
|
||||
(assoc shape :constraints-h :scale :constraints-v :scale))))
|
||||
|
||||
objects' (assoc objects (:id root) root)
|
||||
|
||||
[root-shape changes] (generate-add-component-changes changes root objects' file-id page-id components-v2)
|
||||
|
||||
changes (pcb/update-shapes changes
|
||||
old-root-ids
|
||||
#(dissoc % :component-root)
|
||||
[:component-root])]
|
||||
|
||||
[root (:id root-shape) changes]))
|
|
@ -22,8 +22,10 @@
|
|||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -148,8 +150,6 @@
|
|||
|
||||
[new-component-shape new-component-shapes nil nil]))))
|
||||
|
||||
|
||||
|
||||
(defn generate-duplicate-component
|
||||
"Create a new component copied from the one with the given id."
|
||||
[changes library component-id components-v2]
|
||||
|
@ -1923,3 +1923,295 @@
|
|||
(cond-> changes
|
||||
(some? swap-slot)
|
||||
(generate-sync-head file-full libraries container id components-v2 true))))
|
||||
|
||||
(defn generate-duplicate-flows
|
||||
[changes shapes page ids-map]
|
||||
(let [flows (-> page :options :flows)
|
||||
unames (volatile! (into #{} (map :name flows)))
|
||||
frames-with-flow (->> shapes
|
||||
(filter #(= (:type %) :frame))
|
||||
(filter #(some? (ctp/get-frame-flow flows (:id %)))))]
|
||||
(if-not (empty? frames-with-flow)
|
||||
(let [update-flows (fn [flows]
|
||||
(reduce
|
||||
(fn [flows frame]
|
||||
(let [name (cfh/generate-unique-name @unames "Flow 1")
|
||||
_ (vswap! unames conj name)
|
||||
new-flow {:id (uuid/next)
|
||||
:name name
|
||||
:starting-frame (get ids-map (:id frame))}]
|
||||
(ctp/add-flow flows new-flow)))
|
||||
flows
|
||||
frames-with-flow))]
|
||||
(pcb/update-page-option changes :flows update-flows))
|
||||
changes)))
|
||||
|
||||
(defn generate-duplicate-guides
|
||||
[changes shapes page ids-map delta]
|
||||
(let [guides (get-in page [:options :guides])
|
||||
frames (->> shapes (filter cfh/frame-shape?))
|
||||
|
||||
new-guides
|
||||
(reduce
|
||||
(fn [g frame]
|
||||
(let [new-id (ids-map (:id frame))
|
||||
new-frame (-> frame (gsh/move delta))
|
||||
|
||||
new-guides
|
||||
(->> guides
|
||||
(vals)
|
||||
(filter #(= (:frame-id %) (:id frame)))
|
||||
(map #(-> %
|
||||
(assoc :id (uuid/next))
|
||||
(assoc :frame-id new-id)
|
||||
(assoc :position (if (= (:axis %) :x)
|
||||
(+ (:position %) (- (:x new-frame) (:x frame)))
|
||||
(+ (:position %) (- (:y new-frame) (:y frame))))))))]
|
||||
(cond-> g
|
||||
(not-empty new-guides)
|
||||
(conj (into {} (map (juxt :id identity) new-guides))))))
|
||||
guides
|
||||
frames)]
|
||||
(-> (pcb/with-page changes page)
|
||||
(pcb/set-page-option :guides new-guides))))
|
||||
|
||||
(defn generate-duplicate-component-change
|
||||
[changes objects page component-root parent-id frame-id delta libraries library-data]
|
||||
(let [component-id (:component-id component-root)
|
||||
file-id (:component-file component-root)
|
||||
main-component (ctf/get-component libraries file-id component-id)
|
||||
moved-component (gsh/move component-root delta)
|
||||
pos (gpt/point (:x moved-component) (:y moved-component))
|
||||
origin-frame (get-in page [:objects frame-id])
|
||||
delta (cond-> delta
|
||||
(some? origin-frame)
|
||||
(gpt/subtract (-> origin-frame :selrect gpt/point)))
|
||||
|
||||
instantiate-component
|
||||
#(generate-instantiate-component changes
|
||||
objects
|
||||
file-id
|
||||
(:component-id component-root)
|
||||
pos
|
||||
page
|
||||
libraries
|
||||
(:id component-root)
|
||||
parent-id
|
||||
frame-id
|
||||
{})
|
||||
|
||||
restore-component
|
||||
#(let [restore (prepare-restore-component changes library-data (:component-id component-root) page delta (:id component-root) parent-id frame-id)]
|
||||
[(:shape restore) (:changes restore)])
|
||||
|
||||
[_shape changes]
|
||||
(if (nil? main-component)
|
||||
(restore-component)
|
||||
(instantiate-component))]
|
||||
changes))
|
||||
|
||||
(defn generate-duplicate-shape-change
|
||||
([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data file-id]
|
||||
(generate-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false true))
|
||||
|
||||
([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data file-id frame-id parent-id duplicating-component? child? remove-swap-slot?]
|
||||
(cond
|
||||
(nil? obj)
|
||||
changes
|
||||
|
||||
(ctf/is-main-of-known-component? obj libraries)
|
||||
(generate-duplicate-component-change changes objects page obj parent-id frame-id delta libraries library-data)
|
||||
|
||||
:else
|
||||
(let [frame? (cfh/frame-shape? obj)
|
||||
group? (cfh/group-shape? obj)
|
||||
bool? (cfh/bool-shape? obj)
|
||||
new-id (ids-map (:id obj))
|
||||
parent-id (or parent-id frame-id)
|
||||
parent (get objects parent-id)
|
||||
name (:name obj)
|
||||
|
||||
is-component-root? (or (:saved-component-root obj)
|
||||
;; Backward compatibility
|
||||
(:saved-component-root? obj)
|
||||
(ctk/instance-root? obj))
|
||||
duplicating-component? (or duplicating-component? (ctk/instance-head? obj))
|
||||
is-component-main? (ctk/main-instance? obj)
|
||||
subinstance-head? (ctk/subinstance-head? obj)
|
||||
instance-root? (ctk/instance-root? obj)
|
||||
|
||||
into-component? (and duplicating-component?
|
||||
(ctn/in-any-component? objects parent))
|
||||
|
||||
level-delta (if (some? level-delta)
|
||||
level-delta
|
||||
(ctn/get-nesting-level-delta objects obj parent))
|
||||
new-shape-ref (ctf/advance-shape-ref nil page libraries obj level-delta {:include-deleted? true})
|
||||
|
||||
regenerate-component
|
||||
(fn [changes shape]
|
||||
(let [components-v2 (dm/get-in library-data [:options :components-v2])
|
||||
[_ changes] (generate-add-component-changes changes shape objects file-id (:id page) components-v2)]
|
||||
changes))
|
||||
|
||||
new-obj
|
||||
(-> obj
|
||||
(assoc :id new-id
|
||||
:name name
|
||||
:parent-id parent-id
|
||||
:frame-id frame-id)
|
||||
|
||||
(cond-> (and (not instance-root?)
|
||||
subinstance-head?
|
||||
remove-swap-slot?)
|
||||
(ctk/remove-swap-slot))
|
||||
|
||||
(dissoc :shapes
|
||||
:use-for-thumbnail)
|
||||
|
||||
(cond-> (not is-component-root?)
|
||||
(dissoc :main-instance))
|
||||
|
||||
(cond-> into-component?
|
||||
(dissoc :component-root))
|
||||
|
||||
(cond-> (and (ctk/instance-head? obj)
|
||||
(not into-component?))
|
||||
(assoc :component-root true))
|
||||
|
||||
(cond-> (or frame? group? bool?)
|
||||
(assoc :shapes []))
|
||||
|
||||
(cond-> (and (some? new-shape-ref)
|
||||
(not= new-shape-ref (:shape-ref obj)))
|
||||
(assoc :shape-ref new-shape-ref))
|
||||
|
||||
(gsh/move delta)
|
||||
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))
|
||||
|
||||
(cond-> (ctl/grid-layout? obj)
|
||||
(ctl/remap-grid-cells ids-map)))
|
||||
|
||||
new-obj (cond-> new-obj
|
||||
(not duplicating-component?)
|
||||
(ctk/detach-shape))
|
||||
|
||||
;; We want the first added object to touch it's parent, but not subsequent children
|
||||
changes (-> (pcb/add-object changes new-obj {:ignore-touched (and duplicating-component? child?)})
|
||||
(pcb/amend-last-change #(assoc % :old-id (:id obj)))
|
||||
(cond-> (ctl/grid-layout? objects (:parent-id obj))
|
||||
(-> (pcb/update-shapes [(:parent-id obj)] ctl/assign-cells {:with-objects? true})
|
||||
(pcb/reorder-grid-children [(:parent-id obj)]))))
|
||||
|
||||
changes (cond-> changes
|
||||
(and is-component-root? is-component-main?)
|
||||
(regenerate-component new-obj))
|
||||
|
||||
;; This is needed for the recursive call to find the new object as parent
|
||||
page' (ctst/add-shape (:id new-obj)
|
||||
new-obj
|
||||
{:objects objects}
|
||||
(:frame-id new-obj)
|
||||
(:parent-id new-obj)
|
||||
nil
|
||||
true)]
|
||||
|
||||
(reduce (fn [changes child]
|
||||
(generate-duplicate-shape-change changes
|
||||
(:objects page')
|
||||
page
|
||||
unames
|
||||
update-unames!
|
||||
ids-map
|
||||
child
|
||||
delta
|
||||
level-delta
|
||||
libraries
|
||||
library-data
|
||||
file-id
|
||||
(if frame? new-id frame-id)
|
||||
new-id
|
||||
duplicating-component?
|
||||
true
|
||||
(and remove-swap-slot?
|
||||
;; only remove swap slot of children when the current shape
|
||||
;; is not a subinstance head nor a instance root
|
||||
(not subinstance-head?)
|
||||
(not instance-root?))))
|
||||
changes
|
||||
(map (d/getf objects) (:shapes obj)))))))
|
||||
|
||||
(defn generate-duplicate-changes
|
||||
"Prepare objects to duplicate: generate new id, give them unique names,
|
||||
move to the desired position, and recalculate parents and frames as needed."
|
||||
[changes all-objects page ids delta libraries library-data file-id]
|
||||
(let [shapes (map (d/getf all-objects) ids)
|
||||
unames (volatile! (cfh/get-used-names (:objects page)))
|
||||
update-unames! (fn [new-name] (vswap! unames conj new-name))
|
||||
all-ids (reduce #(into %1 (cons %2 (cfh/get-children-ids all-objects %2))) (d/ordered-set) ids)
|
||||
|
||||
;; We need ids-map for remapping the grid layout. But when duplicating the guides
|
||||
;; we calculate a new one because the components will have created new shapes.
|
||||
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
|
||||
|
||||
changes (-> changes
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects all-objects))
|
||||
changes
|
||||
(->> shapes
|
||||
(reduce #(generate-duplicate-shape-change %1
|
||||
all-objects
|
||||
page
|
||||
unames
|
||||
update-unames!
|
||||
ids-map
|
||||
%2
|
||||
delta
|
||||
nil
|
||||
libraries
|
||||
library-data
|
||||
file-id)
|
||||
changes))
|
||||
|
||||
;; We need to check the changes to get the ids-map
|
||||
ids-map
|
||||
(into {}
|
||||
(comp
|
||||
(filter #(= :add-obj (:type %)))
|
||||
(map #(vector (:old-id %) (-> % :obj :id))))
|
||||
(:redo-changes changes))]
|
||||
|
||||
(-> changes
|
||||
(generate-duplicate-flows shapes page ids-map)
|
||||
(generate-duplicate-guides shapes page ids-map delta))))
|
||||
|
||||
(defn generate-duplicate-changes-update-indices
|
||||
"Updates the changes to correctly set the indexes of the duplicated objects,
|
||||
depending on the index of the original object respect their parent."
|
||||
[changes objects ids]
|
||||
(let [;; index-map is a map that goes from parent-id => vector([id index-in-parent])
|
||||
index-map (reduce (fn [index-map id]
|
||||
(let [parent-id (get-in objects [id :parent-id])
|
||||
parent-index (cfh/get-position-on-parent objects id)]
|
||||
(update index-map parent-id (fnil conj []) [id parent-index])))
|
||||
{}
|
||||
ids)
|
||||
|
||||
inc-indices
|
||||
(fn [[offset result] [id index]]
|
||||
[(inc offset) (conj result [id (+ index offset)])])
|
||||
|
||||
fix-indices
|
||||
(fn [_ entry]
|
||||
(->> entry
|
||||
(sort-by second)
|
||||
(reduce inc-indices [1 []])
|
||||
(second)
|
||||
(into {})))
|
||||
|
||||
objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))]
|
||||
|
||||
(pcb/amend-changes
|
||||
changes
|
||||
(fn [change]
|
||||
(assoc change :index (get objects-indices (:old-id change)))))))
|
||||
|
|
|
@ -4,22 +4,22 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns common-tests.helpers.components
|
||||
(ns app.common.test-helpers.components
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[common-tests.helpers.files :as thf]
|
||||
[common-tests.helpers.ids-map :as thi]
|
||||
[common-tests.helpers.shapes :as ths]))
|
||||
[app.common.types.shape-tree :as ctst]))
|
||||
|
||||
(defn make-component
|
||||
[file label root-label & {:keys [] :as params}]
|
||||
|
@ -33,8 +33,9 @@
|
|||
(let [[_new-root _new-shapes updated-shapes]
|
||||
(ctn/convert-shape-in-component root (:objects page) (:id file))
|
||||
|
||||
updated-root (first updated-shapes)] ; Can't use new-root because it has a new id
|
||||
updated-root (first updated-shapes) ; Can't use new-root because it has a new id
|
||||
|
||||
[path name] (cfh/parse-path-name (:name updated-root))]
|
||||
(thi/set-id! label (:component-id updated-root))
|
||||
|
||||
(ctf/update-file-data
|
||||
|
@ -49,14 +50,15 @@
|
|||
updated-shapes)
|
||||
(ctkl/add-component $ (assoc params
|
||||
:id (:component-id updated-root)
|
||||
:name (:name updated-root)
|
||||
:name name
|
||||
:path path
|
||||
:main-instance-id (:id updated-root)
|
||||
:main-instance-page (:id page)
|
||||
:shapes updated-shapes))))))))
|
||||
|
||||
(defn get-component
|
||||
[file label]
|
||||
(ctkl/get-component (:data file) (thi/id label)))
|
||||
[file label & {:keys [include-deleted?] :or {include-deleted? false}}]
|
||||
(ctkl/get-component (:data file) (thi/id label) include-deleted?))
|
||||
|
||||
(defn get-component-by-id
|
||||
[file id]
|
||||
|
@ -129,6 +131,7 @@
|
|||
(when children-labels
|
||||
(dotimes [idx (count children-labels)]
|
||||
(set-child-label file' copy-root-label idx (nth children-labels idx))))
|
||||
|
||||
file'))
|
||||
|
||||
(defn component-swap
|
|
@ -4,11 +4,11 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns common-tests.helpers.compositions
|
||||
(ns app.common.test-helpers.compositions
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[common-tests.helpers.components :as thc]
|
||||
[common-tests.helpers.shapes :as ths]))
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.shapes :as ths]))
|
||||
|
||||
(defn add-rect
|
||||
[file rect-label & {:keys [] :as params}]
|
||||
|
@ -140,8 +140,8 @@
|
|||
component2-params)))
|
||||
|
||||
(defn add-nested-component-with-copy
|
||||
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label copy2-label
|
||||
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-params]}]
|
||||
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label copy2-root-label
|
||||
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main1-root-label} [:name: Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name: Rect1]
|
||||
|
@ -166,4 +166,4 @@
|
|||
:component2-params component2-params
|
||||
:main2-root-params main2-root-params
|
||||
:nested-head-params nested-head-params)
|
||||
(thc/instantiate-component component2-label copy2-label copy2-params)))
|
||||
(thc/instantiate-component component2-label copy2-root-label copy2-root-params)))
|
|
@ -4,18 +4,19 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns common-tests.helpers.files
|
||||
(ns app.common.test-helpers.files
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.features :as ffeat]
|
||||
[app.common.files.changes :as cfc]
|
||||
[app.common.files.validate :as cfv]
|
||||
[app.common.pprint :refer [pprint]]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.uuid :as uuid]
|
||||
[common-tests.helpers.ids-map :as thi]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; ----- Files
|
||||
|
@ -87,7 +88,7 @@
|
|||
|
||||
;; ----- Debug
|
||||
|
||||
(defn dump-file-type
|
||||
(defn dump-tree
|
||||
"Dump a file using dump-tree function in common.types.file."
|
||||
[file & {:keys [page-label libraries] :as params}]
|
||||
(let [params (-> params
|
||||
|
@ -115,44 +116,74 @@
|
|||
(println "}"))
|
||||
|
||||
(defn- stringify-keys [m keys]
|
||||
(apply str (interpose ", " (map #(str % ": " (get m %)) keys))))
|
||||
(let [kv (-> (select-keys m keys)
|
||||
(assoc :swap-slot (when ((set keys) :swap-slot)
|
||||
(ctk/get-swap-slot m)))
|
||||
(assoc :swap-slot-label (when ((set keys) :swap-slot-label)
|
||||
(when-let [slot (ctk/get-swap-slot m)]
|
||||
(thi/label slot))))
|
||||
(d/without-nils))
|
||||
|
||||
pretty-uuid (fn [id]
|
||||
(let [id (str id)]
|
||||
(str "#" (subs id (- (count id) 6)))))
|
||||
|
||||
format-kv (fn [[k v]]
|
||||
(cond
|
||||
(uuid? v)
|
||||
(str k " " (pretty-uuid v))
|
||||
|
||||
:else
|
||||
(str k " " v)))]
|
||||
|
||||
(when (seq kv)
|
||||
(str " [" (apply str (interpose ", " (map format-kv kv))) "]"))))
|
||||
|
||||
(defn- dump-page-shape
|
||||
[shape keys padding]
|
||||
[shape keys padding show-refs?]
|
||||
(println (str/pad (str padding
|
||||
(when (:main-instance shape) "{")
|
||||
(or (thi/label (:id shape)) "<no-label>")
|
||||
(when (:main-instance shape) "}")
|
||||
(when keys
|
||||
(str " [" (stringify-keys shape keys) "]")))
|
||||
{:length 40 :type :right})
|
||||
(when (and (:main-instance shape) show-refs?) "{")
|
||||
(thi/label (:id shape))
|
||||
(when (and (:main-instance shape) show-refs?) "}")
|
||||
(when (seq keys)
|
||||
(stringify-keys shape keys)))
|
||||
{:length 50 :type :right})
|
||||
(if (nil? (:shape-ref shape))
|
||||
(if (:component-root shape)
|
||||
(str "# [Component " (or (thi/label (:component-id shape)) "<no-label>") "]")
|
||||
(if (and (:component-root shape) show-refs?)
|
||||
(str "# [Component " (thi/label (:component-id shape)) "]")
|
||||
"")
|
||||
(str/format "%s--> %s%s"
|
||||
(cond (:component-root shape) "#"
|
||||
(:component-id shape) "@"
|
||||
:else "-")
|
||||
(if (:component-root shape)
|
||||
(str "[Component " (or (thi/label (:component-id shape)) "<no-label>") "] ")
|
||||
"")
|
||||
(or (thi/label (:shape-ref shape)) "<no-label>")))))
|
||||
(if show-refs?
|
||||
(str/format "%s--> %s%s"
|
||||
(cond (:component-root shape) "#"
|
||||
(:component-id shape) "@"
|
||||
:else "-")
|
||||
(if (:component-root shape)
|
||||
(str "[Component " (thi/label (:component-id shape)) "] ")
|
||||
"")
|
||||
(thi/label (:shape-ref shape)))
|
||||
""))))
|
||||
|
||||
(defn dump-page
|
||||
"Dump the layer tree of the page. Print the label of each shape, and the specified keys."
|
||||
([page keys]
|
||||
(dump-page page uuid/zero "" keys))
|
||||
([page root-id padding keys]
|
||||
(let [lookupf (d/getf (:objects page))
|
||||
root-shape (lookupf root-id)
|
||||
shapes (map lookupf (:shapes root-shape))]
|
||||
(doseq [shape shapes]
|
||||
(dump-page-shape shape keys padding)
|
||||
(dump-page page (:id shape) (str padding " ") keys)))))
|
||||
"Dump the layer tree of the page, showing labels of the shapes.
|
||||
- keys: a list of attributes of the shapes you want to show. In addition, you
|
||||
can add :swap-slot to show the slot id (if any) or :swap-slot-label
|
||||
to show the corresponding label.
|
||||
- show-refs?: if true, the component references will be shown."
|
||||
[page & {:keys [keys root-id padding show-refs?]
|
||||
:or {keys [:name :swap-slot-label] root-id uuid/zero padding "" show-refs? true}}]
|
||||
(let [lookupf (d/getf (:objects page))
|
||||
root-shape (lookupf root-id)
|
||||
shapes (map lookupf (:shapes root-shape))]
|
||||
(doseq [shape shapes]
|
||||
(dump-page-shape shape keys padding show-refs?)
|
||||
(dump-page page
|
||||
:keys keys
|
||||
:root-id (:id shape)
|
||||
:padding (str padding " ")
|
||||
:show-refs? show-refs?))))
|
||||
|
||||
(defn dump-file
|
||||
"Dump the current page of the file, using dump-page above.
|
||||
Example: (thf/dump-file file [:id :touched])"
|
||||
([file] (dump-file file []))
|
||||
([file keys] (dump-page (current-page file) keys)))
|
||||
Example: (thf/dump-file file :keys [:name :swap-slot-label] :show-refs? false)"
|
||||
[file & {:keys [] :as params}]
|
||||
(dump-page (current-page file) params))
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns common-tests.helpers.ids-map
|
||||
(ns app.common.test-helpers.ids-map
|
||||
(:require
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
|
@ -36,7 +36,8 @@
|
|||
(f))
|
||||
|
||||
(defn label [id]
|
||||
(->> @idmap
|
||||
(filter #(= id (val %)))
|
||||
(map key)
|
||||
(first)))
|
||||
(or (->> @idmap
|
||||
(filter #(= id (val %)))
|
||||
(map key)
|
||||
(first))
|
||||
(str "<no-label #" (subs (str id) (- (count (str id)) 6)) ">")))
|
|
@ -4,10 +4,12 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns common-tests.helpers.shapes
|
||||
(ns app.common.test-helpers.shapes
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.file :as ctf]
|
||||
|
@ -15,9 +17,7 @@
|
|||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typographies-list :as cttl]
|
||||
[app.common.types.typography :as ctt]
|
||||
[common-tests.helpers.files :as thf]
|
||||
[common-tests.helpers.ids-map :as thi]))
|
||||
[app.common.types.typography :as ctt]))
|
||||
|
||||
(defn sample-shape
|
||||
[label & {:keys [type] :as params}]
|
|
@ -61,6 +61,10 @@
|
|||
|
||||
(update container :objects update-objects parent-id)))
|
||||
|
||||
(defn parent-of?
|
||||
[parent child]
|
||||
(= (:id parent) (:parent-id child)))
|
||||
|
||||
(defn get-shape
|
||||
"Get a shape identified by id"
|
||||
[container id]
|
||||
|
|
BIN
common/test/cases/chained-components-changes-propagation.penpot
Normal file
BIN
common/test/cases/chained-components-changes-propagation.penpot
Normal file
Binary file not shown.
212
common/test/common_tests/logic/chained_propagation_test.cljc
Normal file
212
common/test/common_tests/logic/chained_propagation_test.cljc
Normal file
|
@ -0,0 +1,212 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns common-tests.logic.chained-propagation-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.container :as ctn]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(defn- first-fill-color [file tag]
|
||||
(-> (ths/get-shape file tag)
|
||||
(:fills)
|
||||
first
|
||||
:fill-color))
|
||||
|
||||
(defn- first-child-fill-color [file tag]
|
||||
(let [shape (ths/get-shape file tag)]
|
||||
(-> (ths/get-shape-by-id file (first (:shapes shape)))
|
||||
(:fills)
|
||||
first
|
||||
:fill-color)))
|
||||
|
||||
;; Related .penpot file: common/test/cases/chained-components-changes-propagation.penpot
|
||||
(t/deftest test-propagation-with-anidated-components
|
||||
(letfn [(setup []
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame :frame-comp-1)
|
||||
(ths/add-sample-shape :rectangle :parent-label :frame-comp-1)
|
||||
(thc/make-component :comp-1 :frame-comp-1)
|
||||
|
||||
(tho/add-frame :frame-comp-2)
|
||||
(thc/instantiate-component :comp-1 :copy-comp-1 :parent-label :frame-comp-2 :children-labels [:rect-comp-2])
|
||||
(thc/make-component :comp-2 :frame-comp-2)
|
||||
|
||||
(tho/add-frame :frame-comp-3)
|
||||
(thc/instantiate-component :comp-2 :copy-comp-2 :parent-label :frame-comp-3 :children-labels [:comp-1-comp-2])
|
||||
(thc/make-component :comp-3 :frame-comp-3)))
|
||||
|
||||
(step-update-color-comp-2 [file]
|
||||
(let [page (thf/current-page file)
|
||||
|
||||
;; Changes to update the color of the contained rectangle in component comp-2
|
||||
changes-update-color-comp-1
|
||||
(cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
(:shapes (ths/get-shape file :copy-comp-1))
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#FF0000")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes-update-color-comp-1)]
|
||||
|
||||
(t/is (= (first-child-fill-color file' :comp-1-comp-2) "#B1B2B5"))
|
||||
file'))
|
||||
|
||||
(step-propagate-comp-2 [file]
|
||||
(let [page (thf/current-page file)
|
||||
file-id (:id file)
|
||||
|
||||
;; Changes to propagate the color changes of component comp-1
|
||||
changes-sync-comp-1 (-> (pcb/empty-changes)
|
||||
(cll/generate-sync-file-changes
|
||||
nil
|
||||
:components
|
||||
file-id
|
||||
(:id (thc/get-component file :comp-2))
|
||||
file-id
|
||||
{file-id file}
|
||||
file-id))
|
||||
|
||||
file' (thf/apply-changes file changes-sync-comp-1)]
|
||||
|
||||
(t/is (= (first-fill-color file' :rect-comp-2) "#FF0000"))
|
||||
(t/is (= (first-child-fill-color file' :comp-1-comp-2) "#FF0000"))
|
||||
file'))
|
||||
|
||||
(step-update-color-comp-3 [file]
|
||||
(let [page (thf/current-page file)
|
||||
page-id (:id page)
|
||||
comp-1-comp-2 (ths/get-shape file :comp-1-comp-2)
|
||||
rect-comp-3 (ths/get-shape-by-id file (first (:shapes comp-1-comp-2)))
|
||||
;; Changes to update the color of the contained rectangle in component comp-3
|
||||
changes-update-color-comp-3
|
||||
(cls/generate-update-shapes (pcb/empty-changes nil page-id)
|
||||
[(:id rect-comp-3)]
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#00FF00")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes-update-color-comp-3)]
|
||||
|
||||
(t/is (= (first-child-fill-color file' :comp-1-comp-2) "#00FF00"))
|
||||
file'))
|
||||
|
||||
(step-reset [file]
|
||||
(let [page (thf/current-page file)
|
||||
file-id (:id file)
|
||||
comp-1-comp-2 (ths/get-shape file :comp-1-comp-2)
|
||||
;; Changes to reset the changes on comp-1 inside comp-3
|
||||
changes-reset (cll/generate-reset-component (pcb/empty-changes)
|
||||
file
|
||||
{file-id file}
|
||||
(ctn/make-container page :page)
|
||||
(:id comp-1-comp-2)
|
||||
true)
|
||||
file' (thf/apply-changes file changes-reset)]
|
||||
|
||||
(t/is (= (first-child-fill-color file' :comp-1-comp-2) "#FF0000"))
|
||||
file'))]
|
||||
|
||||
(-> (setup)
|
||||
step-update-color-comp-2
|
||||
step-propagate-comp-2
|
||||
step-update-color-comp-3
|
||||
step-reset)))
|
||||
|
||||
(t/deftest test-propagation-with-deleted-component
|
||||
(letfn [(setup []
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame :frame-comp-4)
|
||||
(ths/add-sample-shape :rectangle :parent-label :frame-comp-4)
|
||||
(thc/make-component :comp-4 :frame-comp-4)
|
||||
|
||||
(tho/add-frame :frame-comp-5)
|
||||
(thc/instantiate-component :comp-4 :copy-comp-4 :parent-label :frame-comp-5 :children-labels [:rect-comp-5])
|
||||
(thc/make-component :comp-5 :frame-comp-5)
|
||||
|
||||
(tho/add-frame :frame-comp-6)
|
||||
(thc/instantiate-component :comp-5 :copy-comp-5 :parent-label :frame-comp-6 :children-labels [:comp-4-comp-5])
|
||||
(thc/make-component :comp-6 :frame-comp-6)))
|
||||
|
||||
(step-delete-comp-5 [file]
|
||||
(let [page (thf/current-page file)
|
||||
;; Changes to delete comp-5
|
||||
[_ changes-delete] (cls/generate-delete-shapes (pcb/empty-changes nil (:id page))
|
||||
file
|
||||
page
|
||||
(:objects page)
|
||||
#{(-> (ths/get-shape file :frame-comp-5)
|
||||
:id)}
|
||||
{:components-v2 true})
|
||||
|
||||
file' (thf/apply-changes file changes-delete)]
|
||||
(t/is (= (first-child-fill-color file' :comp-4-comp-5) "#B1B2B5"))
|
||||
file'))
|
||||
|
||||
(step-update-color-comp-4 [file]
|
||||
(let [page (thf/current-page file)
|
||||
;; Changes to update the color of the contained rectangle in component comp-4
|
||||
changes-update-color-comp-4
|
||||
(cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
[(-> (ths/get-shape file :rectangle)
|
||||
:id)]
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#FF0000")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes-update-color-comp-4)]
|
||||
(t/is (= (first-fill-color file' :rectangle) "#FF0000"))
|
||||
file'))
|
||||
|
||||
(step-propagate-comp-4 [file]
|
||||
(let [file-id (:id file)
|
||||
;; Changes to propagate the color changes of component comp-4
|
||||
changes-sync-comp-4 (-> (pcb/empty-changes)
|
||||
(cll/generate-sync-file-changes
|
||||
nil
|
||||
:components
|
||||
file-id
|
||||
(:id (thc/get-component file :comp-4))
|
||||
file-id
|
||||
{file-id file}
|
||||
file-id))
|
||||
|
||||
file' (thf/apply-changes file changes-sync-comp-4)]
|
||||
file'))
|
||||
|
||||
(step-propagate-comp-5 [file]
|
||||
(let [file-id (:id file)
|
||||
;; Changes to propagate the color changes of component comp-5
|
||||
changes-sync-comp-5 (-> (pcb/empty-changes)
|
||||
(cll/generate-sync-file-changes
|
||||
nil
|
||||
:components
|
||||
file-id
|
||||
(:id (thc/get-component file :comp-5))
|
||||
file-id
|
||||
{file-id file}
|
||||
file-id))
|
||||
file' (thf/apply-changes file changes-sync-comp-5)]
|
||||
(t/is (= (first-child-fill-color file' :comp-4-comp-5) "#FF0000"))
|
||||
file'))]
|
||||
|
||||
(-> (setup)
|
||||
step-delete-comp-5
|
||||
step-update-color-comp-4
|
||||
step-propagate-comp-4
|
||||
step-propagate-comp-5)))
|
610
common/test/common_tests/logic/comp_creation_test.cljc
Normal file
610
common/test/common_tests/logic/comp_creation_test.cljc
Normal file
|
@ -0,0 +1,610 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns common-tests.logic.comp-creation-test
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.shapes-helpers :as cfsh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest test-add-component-from-single-frame
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(ths/add-sample-shape :frame1 :type :frame))
|
||||
|
||||
page (thf/current-page file)
|
||||
frame1 (ths/get-shape file :frame1)
|
||||
|
||||
;; ==== Action
|
||||
[_ component-id changes]
|
||||
(cll/generate-add-component (pcb/empty-changes)
|
||||
[frame1]
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
nil)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component (thc/get-component-by-id file' component-id)
|
||||
root (ths/get-shape-by-id file' (:main-instance-id component))
|
||||
frame1' (ths/get-shape file' :frame1)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? component))
|
||||
(t/is (some? root))
|
||||
(t/is (some? frame1'))
|
||||
(t/is (= (:id root) (:id frame1')))
|
||||
(t/is (ctk/main-instance? root))
|
||||
(t/is (ctk/main-instance-of? (:id root) (:id page) component))))
|
||||
|
||||
(t/deftest test-add-component-from-single-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(ths/add-sample-shape :shape1 :type :rect))
|
||||
|
||||
page (thf/current-page file)
|
||||
shape1 (ths/get-shape file :shape1)
|
||||
|
||||
;; ==== Action
|
||||
[_ component-id changes]
|
||||
(cll/generate-add-component (pcb/empty-changes)
|
||||
[shape1]
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component (thc/get-component-by-id file' component-id)
|
||||
root (ths/get-shape-by-id file' (:main-instance-id component))
|
||||
shape1' (ths/get-shape file' :shape1)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? component))
|
||||
(t/is (some? root))
|
||||
(t/is (some? shape1'))
|
||||
(t/is (ctst/parent-of? root shape1'))
|
||||
(t/is (= (:type root) :frame))
|
||||
(t/is (ctk/main-instance? root))
|
||||
(t/is (ctk/main-instance-of? (:id root) (:id page) component))))
|
||||
|
||||
(t/deftest test-add-component-from-several-shapes
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(ths/add-sample-shape :shape1 :type :rect)
|
||||
(ths/add-sample-shape :shape2 :type :rect))
|
||||
|
||||
page (thf/current-page file)
|
||||
shape1 (ths/get-shape file :shape1)
|
||||
shape2 (ths/get-shape file :shape2)
|
||||
|
||||
;; ==== Action
|
||||
[_ component-id changes]
|
||||
(cll/generate-add-component (pcb/empty-changes)
|
||||
[shape1 shape2]
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component (thc/get-component-by-id file' component-id)
|
||||
root (ths/get-shape-by-id file' (:main-instance-id component))
|
||||
shape1' (ths/get-shape file' :shape1)
|
||||
shape2' (ths/get-shape file' :shape2)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? component))
|
||||
(t/is (some? root))
|
||||
(t/is (some? shape1'))
|
||||
(t/is (some? shape2'))
|
||||
(t/is (ctst/parent-of? root shape1'))
|
||||
(t/is (ctst/parent-of? root shape2'))
|
||||
(t/is (= (:type root) :frame))
|
||||
(t/is (ctk/main-instance? root))
|
||||
(t/is (ctk/main-instance-of? (:id root) (:id page) component))))
|
||||
|
||||
(t/deftest test-add-component-from-several-frames
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(ths/add-sample-shape :frame1 :type :frame)
|
||||
(ths/add-sample-shape :frame2 :type :frame))
|
||||
|
||||
page (thf/current-page file)
|
||||
frame1 (ths/get-shape file :frame1)
|
||||
frame2 (ths/get-shape file :frame2)
|
||||
|
||||
;; ==== Action
|
||||
[_ component-id changes]
|
||||
(cll/generate-add-component (pcb/empty-changes)
|
||||
[frame1 frame2]
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component (thc/get-component-by-id file' component-id)
|
||||
root (ths/get-shape-by-id file' (:main-instance-id component))
|
||||
frame1' (ths/get-shape file' :frame1)
|
||||
frame2' (ths/get-shape file' :frame2)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? component))
|
||||
(t/is (some? root))
|
||||
(t/is (some? frame1'))
|
||||
(t/is (some? frame2'))
|
||||
(t/is (ctst/parent-of? root frame1'))
|
||||
(t/is (ctst/parent-of? root frame2'))
|
||||
(t/is (= (:type root) :frame))
|
||||
(t/is (ctk/main-instance? root))
|
||||
(t/is (ctk/main-instance-of? (:id root) (:id page) component))))
|
||||
|
||||
(t/deftest test-add-component-from-frame-with-children
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(ths/add-sample-shape :frame1 :type :frame)
|
||||
(ths/add-sample-shape :shape1 :type :rect :parent-label :frame1)
|
||||
(ths/add-sample-shape :shape2 :type :rect :parent-label :frame1))
|
||||
|
||||
page (thf/current-page file)
|
||||
frame1 (ths/get-shape file :frame1)
|
||||
|
||||
;; ==== Action
|
||||
[_ component-id changes]
|
||||
(cll/generate-add-component (pcb/empty-changes)
|
||||
[frame1]
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
nil)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component (thc/get-component-by-id file' component-id)
|
||||
root (ths/get-shape-by-id file' (:main-instance-id component))
|
||||
frame1' (ths/get-shape file' :frame1)
|
||||
shape1' (ths/get-shape file' :shape1)
|
||||
shape2' (ths/get-shape file' :shape2)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? component))
|
||||
(t/is (some? root))
|
||||
(t/is (some? frame1'))
|
||||
(t/is (= (:id root) (:id frame1')))
|
||||
(t/is (ctst/parent-of? frame1' shape1'))
|
||||
(t/is (ctst/parent-of? frame1' shape2'))
|
||||
(t/is (ctk/main-instance? root))
|
||||
(t/is (ctk/main-instance-of? (:id root) (:id page) component))))
|
||||
|
||||
(t/deftest test-add-component-from-copy
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:copy1-root))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy1-root (ths/get-shape file :copy1-root)
|
||||
|
||||
;; ==== Action
|
||||
[_ component2-id changes]
|
||||
(cll/generate-add-component (pcb/empty-changes)
|
||||
[copy1-root]
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component2' (thc/get-component-by-id file' component2-id)
|
||||
root2' (ths/get-shape-by-id file' (:main-instance-id component2'))
|
||||
copy1-root' (ths/get-shape file' :copy1-root)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? component2'))
|
||||
(t/is (some? root2'))
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (ctst/parent-of? root2' copy1-root'))
|
||||
(t/is (ctk/main-instance? root2'))
|
||||
(t/is (ctk/main-instance-of? (:id root2') (:id page) component2'))))
|
||||
|
||||
(t/deftest test-rename-component
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:name "Test component before"))
|
||||
|
||||
component (thc/get-component file :component1)
|
||||
|
||||
;; ==== Action
|
||||
changes (cll/generate-rename-component (pcb/empty-changes)
|
||||
(:id component)
|
||||
"Test component after"
|
||||
(:data file)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component' (thc/get-component file' :component1)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= (:name component') "Test component after"))))
|
||||
|
||||
(t/deftest test-duplicate-component
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1
|
||||
:main1-root
|
||||
:main1-child))
|
||||
|
||||
component (thc/get-component file :component1)
|
||||
|
||||
;; ==== Action
|
||||
changes (cll/generate-duplicate-component (pcb/empty-changes)
|
||||
file
|
||||
(:id component)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
components' (ctkl/components-seq (:data file'))
|
||||
component1' (d/seek #(= (:id %) (thi/id :component1)) components')
|
||||
component2' (d/seek #(not= (:id %) (thi/id :component1)) components')
|
||||
root1' (ths/get-shape-by-id file' (:main-instance-id component1'))
|
||||
root2' (ths/get-shape-by-id file' (:main-instance-id component2'))
|
||||
child1' (ths/get-shape-by-id file' (first (:shapes root1')))
|
||||
child2' (ths/get-shape-by-id file' (first (:shapes root2')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= 2 (count components')))
|
||||
(t/is (some? component1'))
|
||||
(t/is (some? component2'))
|
||||
(t/is (some? root1'))
|
||||
(t/is (some? root2'))
|
||||
(t/is (= (thi/id :main1-root) (:id root1')))
|
||||
(t/is (not= (thi/id :main1-root) (:id root2')))
|
||||
(t/is (some? child1'))
|
||||
(t/is (some? child2'))
|
||||
(t/is (= (thi/id :main1-child) (:id child1')))
|
||||
(t/is (not= (thi/id :main1-child) (:id child2')))))
|
||||
|
||||
(t/deftest test-delete-component
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:copy1-root))
|
||||
|
||||
page (thf/current-page file)
|
||||
root (ths/get-shape file :main1-root)
|
||||
|
||||
;; ==== Action
|
||||
[_ changes]
|
||||
(cls/generate-delete-shapes (pcb/empty-changes)
|
||||
file
|
||||
page
|
||||
(:objects page)
|
||||
#{(:id root)}
|
||||
{:components-v2 true})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component1' (thc/get-component file' :component1 :include-deleted? true)
|
||||
copy1-root' (ths/get-shape file' :copy1-root)
|
||||
|
||||
main1-root' (ths/get-shape file' :main1-root)
|
||||
main1-child' (ths/get-shape file' :main1-child)
|
||||
|
||||
saved-objects (:objects component1')
|
||||
saved-main1-root' (get saved-objects (thi/id :main1-root))
|
||||
saved-main1-child' (get saved-objects (thi/id :main1-child))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (true? (:deleted component1')))
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (nil? main1-root'))
|
||||
(t/is (nil? main1-child'))
|
||||
(t/is (some? saved-main1-root'))
|
||||
(t/is (some? saved-main1-child'))))
|
||||
|
||||
(t/deftest test-restore-component
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:copy1-root))
|
||||
|
||||
page (thf/current-page file)
|
||||
root (ths/get-shape file :main1-root)
|
||||
|
||||
;; ==== Action
|
||||
[_ changes]
|
||||
(cls/generate-delete-shapes (pcb/empty-changes)
|
||||
file
|
||||
page
|
||||
(:objects page)
|
||||
#{(:id root)}
|
||||
{:components-v2 true})
|
||||
|
||||
file-deleted (thf/apply-changes file changes)
|
||||
page-deleted (thf/current-page file-deleted)
|
||||
|
||||
changes (cll/generate-restore-component (pcb/empty-changes)
|
||||
(:data file-deleted)
|
||||
(thi/id :component1)
|
||||
(:id file-deleted)
|
||||
page-deleted
|
||||
(:objects page-deleted))
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component1' (thc/get-component file' :component1 :include-deleted? false)
|
||||
copy1-root' (ths/get-shape file' :copy1-root)
|
||||
|
||||
main1-root' (ths/get-shape file' :main1-root)
|
||||
main1-child' (ths/get-shape file' :main1-child)
|
||||
|
||||
saved-objects' (:objects component1')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (nil? (:deleted component1')))
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (some? main1-root'))
|
||||
(t/is (some? main1-child'))
|
||||
(t/is (ctk/main-instance? main1-root'))
|
||||
(t/is (ctk/main-instance-of? (:id main1-root') (:id page) component1'))
|
||||
(t/is (nil? saved-objects'))))
|
||||
|
||||
(t/deftest test-instantiate-component
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1
|
||||
:main1-root
|
||||
:main1-child))
|
||||
|
||||
page (thf/current-page file)
|
||||
component (thc/get-component file :component1)
|
||||
|
||||
;; ==== Action
|
||||
[new-shape changes]
|
||||
(cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page)) ;; This may not be moved to generate
|
||||
(pcb/with-objects (:objects page))) ;; because in some cases the objects
|
||||
(:objects page) ;; not the same as those on the page
|
||||
(:id file)
|
||||
(:id component)
|
||||
(gpt/point 1000 1000)
|
||||
page
|
||||
{(:id file) file})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component' (thc/get-component file' :component1)
|
||||
main1-root' (ths/get-shape file' :main1-root)
|
||||
main1-child' (ths/get-shape file' :main1-child)
|
||||
copy1-root' (ths/get-shape-by-id file' (:id new-shape))
|
||||
copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? main1-root'))
|
||||
(t/is (some? main1-child'))
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-instantiate-component-from-lib
|
||||
(let [;; ==== Setup
|
||||
library (-> (thf/sample-file :library1)
|
||||
(tho/add-simple-component :component1
|
||||
:main1-root
|
||||
:main1-child))
|
||||
|
||||
file (thf/sample-file :file1)
|
||||
|
||||
page (thf/current-page file)
|
||||
component (thc/get-component library :component1)
|
||||
|
||||
;; ==== Action
|
||||
[new-shape changes]
|
||||
(cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))
|
||||
(pcb/with-objects (:objects page)))
|
||||
(:objects page)
|
||||
(:id library)
|
||||
(:id component)
|
||||
(gpt/point 1000 1000)
|
||||
page
|
||||
{(:id file) file
|
||||
(:id library) library})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component' (thc/get-component library :component1)
|
||||
main1-root' (ths/get-shape library :main1-root)
|
||||
main1-child' (ths/get-shape library :main1-child)
|
||||
copy1-root' (ths/get-shape-by-id file' (:id new-shape))
|
||||
copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? main1-root'))
|
||||
(t/is (some? main1-child'))
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-instantiate-nested-component
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head))
|
||||
|
||||
page (thf/current-page file)
|
||||
component (thc/get-component file :component1)
|
||||
|
||||
;; ==== Action
|
||||
[new-shape changes]
|
||||
(cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))
|
||||
(pcb/with-objects (:objects page)))
|
||||
(:objects page)
|
||||
(:id file)
|
||||
(:id component)
|
||||
(gpt/point 1000 1000)
|
||||
page
|
||||
{(:id file) file})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component' (thc/get-component file' :component1)
|
||||
main1-root' (ths/get-shape file' :main1-root)
|
||||
main1-child' (ths/get-shape file' :main1-child)
|
||||
copy1-root' (ths/get-shape-by-id file' (:id new-shape))
|
||||
copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? main1-root'))
|
||||
(t/is (some? main1-child'))
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-instantiate-nested-component-from-lib
|
||||
(let [;; ==== Setup
|
||||
library (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head))
|
||||
|
||||
file (thf/sample-file :file1)
|
||||
|
||||
page (thf/current-page file)
|
||||
component (thc/get-component library :component1)
|
||||
|
||||
;; ==== Action
|
||||
[new-shape changes]
|
||||
(cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))
|
||||
(pcb/with-objects (:objects page)))
|
||||
(:objects page)
|
||||
(:id library)
|
||||
(:id component)
|
||||
(gpt/point 1000 1000)
|
||||
page
|
||||
{(:id file) file
|
||||
(:id library) library})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component' (thc/get-component library :component1)
|
||||
main1-root' (ths/get-shape library :main1-root)
|
||||
main1-child' (ths/get-shape library :main1-child)
|
||||
copy1-root' (ths/get-shape-by-id file' (:id new-shape))
|
||||
copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? main1-root'))
|
||||
(t/is (some? main1-child'))
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (some? copy1-child'))
|
||||
(t/is (ctk/instance-root? copy1-root'))
|
||||
(t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
|
||||
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
|
||||
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
|
||||
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
|
||||
|
||||
(t/deftest test-detach-copy
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:copy1-root))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy1-root (ths/get-shape file :copy1-root)
|
||||
|
||||
;; ==== Action
|
||||
changes (cll/generate-detach-component (pcb/empty-changes)
|
||||
(:id copy1-root)
|
||||
(:data file)
|
||||
(:id page)
|
||||
{(:id file) file})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy1-root' (ths/get-shape file' :copy1-root)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy1-root'))
|
||||
(t/is (not (ctk/instance-head? copy1-root')))
|
||||
(t/is (not (ctk/in-component-copy? copy1-root')))))
|
|
@ -7,44 +7,45 @@
|
|||
(ns common-tests.logic.comp-remove-swap-slots-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]
|
||||
[common-tests.helpers.components :as thc]
|
||||
[common-tests.helpers.compositions :as tho]
|
||||
[common-tests.helpers.files :as thf]
|
||||
[common-tests.helpers.ids-map :as thi]
|
||||
[common-tests.helpers.shapes :as ths]))
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
|
||||
;; Related .penpot file: common/test/cases/remove-swap-slots.penpot
|
||||
(defn- setup-file
|
||||
[]
|
||||
;; :frame-b1 [:id: 3aee2370-44e4-81c8-8004-46e56a459d70, :touched: ]
|
||||
;; :blue1 [:id: 3aee2370-44e4-81c8-8004-46e56a45fc55, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d75}]
|
||||
;; :green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a45fc56, :touched: ]
|
||||
;; :blue-copy-in-green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a4631a4, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d6f}]
|
||||
;; :frame-yellow [:id: 3aee2370-44e4-81c8-8004-46e56a459d73, :touched: ]
|
||||
;; :frame-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6c, :touched: ]
|
||||
;; :red-copy-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6f, :touched: ]
|
||||
;; :frame-blue [:id: 3aee2370-44e4-81c8-8004-46e56a459d69, :touched: ]
|
||||
;; :frame-b2 [:id: 3aee2370-44e4-81c8-8004-46e56a4631a5, :touched: ]
|
||||
;; :frame-red [:id: 3aee2370-44e4-81c8-8004-46e56a459d66, :touched: ]
|
||||
|
||||
;; {:frame-red} [:name Frame1] # [Component :red]
|
||||
;; {:frame-blue} [:name Frame1] # [Component :blue]
|
||||
;; {:frame-green} [:name Frame1] # [Component :green]
|
||||
;; :red-copy-green [:name Frame1] @--> :frame-red
|
||||
;; {:frame-b1} [:name Frame1] # [Component :b1]
|
||||
;; :blue1 [:name Frame1, :swap-slot-label :red-copy] @--> :frame-blue
|
||||
;; :frame-yellow [:name Frame1]
|
||||
;; :green-copy [:name Frame1] @--> :frame-green
|
||||
;; :blue-copy-in-green-copy [:name Frame1, :swap-slot-label :red-copy-green] @--> :frame-blue
|
||||
;; {:frame-b2} [:name Frame1] # [Component :b2]
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame :frame-red)
|
||||
(thc/make-component :red :frame-red)
|
||||
(tho/add-frame :frame-blue)
|
||||
(tho/add-frame :frame-blue :name "frame-blue")
|
||||
(thc/make-component :blue :frame-blue)
|
||||
(tho/add-frame :frame-green)
|
||||
(thc/make-component :green :frame-green)
|
||||
(thc/instantiate-component :red :red-copy-green :parent-label :frame-green)
|
||||
(tho/add-frame :frame-b1)
|
||||
(thc/make-component :b1 :frame-b1)
|
||||
(tho/add-frame :frame-yellow :parent-label :frame-b1)
|
||||
(tho/add-frame :frame-yellow :parent-label :frame-b1 :name "frame-yellow")
|
||||
(thc/instantiate-component :red :red-copy :parent-label :frame-b1)
|
||||
(thc/component-swap :red-copy :blue :blue1)
|
||||
(thc/instantiate-component :green :green-copy :parent-label :frame-b1 :children-labels [:red-copy-in-green-copy])
|
||||
|
@ -52,7 +53,7 @@
|
|||
(tho/add-frame :frame-b2)
|
||||
(thc/make-component :b2 :frame-b2)))
|
||||
|
||||
(t/deftest test-keep-swap-slot-relocating-blue1-to-root
|
||||
(t/deftest test-remove-swap-slot-relocating-blue1-to-root
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
|
||||
|
@ -81,7 +82,7 @@
|
|||
(t/is (some? blue1'))
|
||||
(t/is (nil? (ctk/get-swap-slot blue1')))))
|
||||
|
||||
(t/deftest test-keep-swap-slot-move-blue1-to-root
|
||||
(t/deftest test-remove-swap-slot-move-blue1-to-root
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
|
@ -111,7 +112,7 @@
|
|||
(t/is (nil? (ctk/get-swap-slot blue1')))))
|
||||
|
||||
|
||||
(t/deftest test-keep-swap-slot-relocating-blue1-to-b2
|
||||
(t/deftest test-remove-swap-slot-relocating-blue1-to-b2
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
|
@ -141,7 +142,7 @@
|
|||
(t/is (some? blue1'))
|
||||
(t/is (nil? (ctk/get-swap-slot blue1')))))
|
||||
|
||||
(t/deftest test-keep-swap-slot-move-blue1-to-b2
|
||||
(t/deftest test-remove-swap-slot-move-blue1-to-b2
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
|
@ -172,7 +173,7 @@
|
|||
(t/is (some? blue1'))
|
||||
(t/is (nil? (ctk/get-swap-slot blue1')))))
|
||||
|
||||
(t/deftest test-keep-swap-slot-relocating-yellow-to-root
|
||||
(t/deftest test-remove-swap-slot-relocating-yellow-to-root
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
|
@ -215,7 +216,7 @@
|
|||
(t/is (some? blue1''))
|
||||
(t/is (nil? (ctk/get-swap-slot blue1'')))))
|
||||
|
||||
(t/deftest test-keep-swap-slot-move-yellow-to-root
|
||||
(t/deftest test-remove-swap-slot-move-yellow-to-root
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
|
@ -259,7 +260,7 @@
|
|||
(t/is (nil? (ctk/get-swap-slot blue1'')))))
|
||||
|
||||
|
||||
(t/deftest test-keep-swap-slot-relocating-yellow-to-b2
|
||||
(t/deftest test-remove-swap-slot-relocating-yellow-to-b2
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
|
@ -303,7 +304,7 @@
|
|||
(t/is (some? blue1''))
|
||||
(t/is (nil? (ctk/get-swap-slot blue1'')))))
|
||||
|
||||
(t/deftest test-keep-swap-slot-move-yellow-to-b2
|
||||
(t/deftest test-remove-swap-slot-move-yellow-to-b2
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
|
@ -347,3 +348,105 @@
|
|||
;; blue1 has not swap-id after move
|
||||
(t/is (some? blue1''))
|
||||
(t/is (nil? (ctk/get-swap-slot blue1'')))))
|
||||
|
||||
(defn- find-duplicated-shape
|
||||
[original-shape page]
|
||||
;; duplicated shape has the same name, the same parent, and doesn't have a label
|
||||
(->> (vals (:objects page))
|
||||
(filter #(and (= (:name %) (:name original-shape))
|
||||
(= (:parent-id %) (:parent-id original-shape))
|
||||
(str/starts-with? (thi/label (:id %)) "<no-label")))
|
||||
first))
|
||||
|
||||
(t/deftest test-remove-swap-slot-duplicating-blue1
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
|
||||
page (thf/current-page file)
|
||||
blue1 (ths/get-shape file :blue1)
|
||||
|
||||
;; ==== Action
|
||||
changes (-> (pcb/empty-changes nil)
|
||||
(cll/generate-duplicate-changes (:objects page) ;; objects
|
||||
page ;; page
|
||||
#{(:id blue1)} ;; ids
|
||||
(gpt/point 0 0) ;; delta
|
||||
{(:id file) file} ;; libraries
|
||||
(:data file) ;; library-data
|
||||
(:id file)) ;; file-id
|
||||
(cll/generate-duplicate-changes-update-indices (:objects page) ;; objects
|
||||
#{(:id blue1)})) ;; ids
|
||||
|
||||
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
page' (thf/current-page file')
|
||||
blue1' (ths/get-shape file' :blue1)
|
||||
duplicated-blue1' (find-duplicated-shape blue1' page')]
|
||||
|
||||
;; ==== Check
|
||||
|
||||
;; blue1 has swap-id
|
||||
(t/is (some? (ctk/get-swap-slot blue1')))
|
||||
|
||||
;; duplicated-blue1 has not swap-id
|
||||
(t/is (some? duplicated-blue1'))
|
||||
(t/is (nil? (ctk/get-swap-slot duplicated-blue1')))))
|
||||
|
||||
(t/deftest test-remove-swap-slot-duplicate-yellow
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
blue1 (ths/get-shape file :blue1)
|
||||
yellow (ths/get-shape file :frame-yellow)
|
||||
|
||||
;; ==== Action
|
||||
;; Move blue1 into yellow
|
||||
changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil)
|
||||
#{(:id blue1)} ;; ids
|
||||
(:id yellow) ;; frame-id
|
||||
(:id page) ;; page-id
|
||||
(:objects page) ;; objects
|
||||
0 ;; drop-index
|
||||
nil) ;; cell
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
page' (thf/current-page file')
|
||||
yellow' (ths/get-shape file' :frame-yellow)
|
||||
|
||||
;; Duplicate yellow
|
||||
changes' (-> (pcb/empty-changes nil)
|
||||
(cll/generate-duplicate-changes (:objects page') ;; objects
|
||||
page' ;; page
|
||||
#{(:id yellow')} ;; ids
|
||||
(gpt/point 0 0) ;; delta
|
||||
{(:id file') file'} ;; libraries
|
||||
(:data file') ;; library-data
|
||||
(:id file')) ;; file-id
|
||||
(cll/generate-duplicate-changes-update-indices (:objects page') ;; objects
|
||||
#{(:id yellow')})) ;; ids
|
||||
|
||||
file'' (thf/apply-changes file' changes')
|
||||
|
||||
;; ==== Get
|
||||
page'' (thf/current-page file'')
|
||||
blue1'' (ths/get-shape file'' :blue1)
|
||||
yellow'' (ths/get-shape file'' :frame-yellow)
|
||||
|
||||
|
||||
duplicated-yellow'' (find-duplicated-shape yellow'' page'')
|
||||
duplicated-blue1-id'' (-> duplicated-yellow''
|
||||
:shapes
|
||||
first)
|
||||
duplicated-blue1'' (get (:objects page'') duplicated-blue1-id'')]
|
||||
|
||||
;; ==== Check
|
||||
|
||||
;; blue1'' has swap-id
|
||||
(t/is (some? (ctk/get-swap-slot blue1'')))
|
||||
|
||||
;; duplicated-blue1'' has not swap-id
|
||||
(t/is (some? duplicated-blue1''))
|
||||
(t/is (nil? (ctk/get-swap-slot duplicated-blue1'')))))
|
||||
|
|
359
common/test/common_tests/logic/comp_reset_test.cljc
Normal file
359
common/test/common_tests/logic/comp_reset_test.cljc
Normal file
|
@ -0,0 +1,359 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns common-tests.logic.comp-reset-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest test-reset-after-changing-attribute
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:main-child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
copy-child (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file-mdf (thf/apply-changes file changes)
|
||||
page-mdf (thf/current-page file-mdf)
|
||||
|
||||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#abcdef"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-reset-from-library
|
||||
(let [;; ==== Setup
|
||||
library (-> (thf/sample-file :library :is-shared true)
|
||||
(tho/add-simple-component :component1 :main-root :main-child
|
||||
:child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
|
||||
file (-> (thf/sample-file :file)
|
||||
(thc/instantiate-component :component1 :copy-root
|
||||
:library library
|
||||
:children-labels [:copy-child]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
copy-child (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file-mdf (thf/apply-changes file changes)
|
||||
page-mdf (thf/current-page file-mdf)
|
||||
|
||||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf
|
||||
(:id library) library}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#abcdef"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-reset-after-adding-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]})
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||||
;; will not have any effect, and so the parent shape won't also be touched.
|
||||
changes (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id copy-root)} ; parents
|
||||
(thi/id :copy-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
0 ; to-index
|
||||
#{(thi/id :free-shape)}) ; ids
|
||||
|
||||
file-mdf (thf/apply-changes file changes)
|
||||
page-mdf (thf/current-page file-mdf)
|
||||
|
||||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-reset-after-deleting-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
copy-child (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action will not
|
||||
;; delete the child shape, but hide it (thus setting the visibility group).
|
||||
[_all-parents changes]
|
||||
(cls/generate-delete-shapes (pcb/empty-changes)
|
||||
file
|
||||
page
|
||||
(:objects page)
|
||||
#{(:id copy-child)}
|
||||
{:components-v2 true})
|
||||
|
||||
file-mdf (thf/apply-changes file changes)
|
||||
page-mdf (thf/current-page file-mdf)
|
||||
|
||||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-reset-after-moving-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-component-with-many-children-and-copy :component1
|
||||
:main-root
|
||||
[:main-child1 :main-child2 :main-child3]
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]})
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
copy-child1 (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||||
;; will not have any effect, and so the parent shape won't also be touched.
|
||||
changes (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id copy-child1)} ; parents
|
||||
(thi/id :copy-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
2 ; to-index
|
||||
#{(:id copy-child1)}) ; ids
|
||||
|
||||
file-mdf (thf/apply-changes file changes)
|
||||
page-mdf (thf/current-page file-mdf)
|
||||
|
||||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy-root)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-reset-after-changing-upper
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:main2-root-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
page (thf/current-page file)
|
||||
copy2-root (ths/get-shape file :copy2-root)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy2-root)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file-mdf (thf/apply-changes file changes)
|
||||
page-mdf (thf/current-page file-mdf)
|
||||
|
||||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy2-root)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
fills' (:fills copy2-root')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#abcdef"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))))
|
||||
|
||||
(t/deftest test-reset-after-changing-lower
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:copy2-root-params {:children-labels [:copy2-child]}))
|
||||
page (thf/current-page file)
|
||||
copy2-root (ths/get-shape file :copy2-root)
|
||||
copy2-child (ths/get-shape file :copy2-child)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy2-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file-mdf (thf/apply-changes file changes)
|
||||
page-mdf (thf/current-page file-mdf)
|
||||
|
||||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||||
file-mdf
|
||||
{(:id file-mdf) file-mdf}
|
||||
page-mdf
|
||||
(:id copy2-root)
|
||||
true)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
copy2-child' (ths/get-shape file' :copy2-child)
|
||||
fills' (:fills copy2-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (some? copy2-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#FFFFFF"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))
|
||||
(t/is (= (:touched copy2-child') nil))))
|
492
common/test/common_tests/logic/comp_sync_test.cljc
Normal file
492
common/test/common_tests/logic/comp_sync_test.cljc
Normal file
|
@ -0,0 +1,492 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns common-tests.logic.comp-sync-test
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest test-sync-when-changing-attribute
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:main-child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
page (thf/current-page file)
|
||||
main-child (ths/get-shape file :main-child)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id main-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component1)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
file' (thf/apply-changes updated-file changes2)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-sync-when-changing-attribute-from-library
|
||||
(let [;; ==== Setup
|
||||
library (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:main-child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
|
||||
file (-> (thf/sample-file :file)
|
||||
(thc/instantiate-component :component1 :copy-root
|
||||
:library library
|
||||
:children-labels [:copy-child]))
|
||||
|
||||
page (thf/current-page library)
|
||||
main-child (ths/get-shape library :main-child)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id main-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
updated-library (thf/apply-changes library changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id file)
|
||||
(thi/id :component1)
|
||||
(:id updated-library)
|
||||
{(:id updated-library) updated-library
|
||||
(:id file) file}
|
||||
(:id file))
|
||||
|
||||
file' (thf/apply-changes file changes2)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-sync-when-changing-attribute-preserve-touched
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:main-child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
page (thf/current-page file)
|
||||
main-child (ths/get-shape file :main-child)
|
||||
copy-child (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (-> (pcb/empty-changes nil (:id page))
|
||||
(cls/generate-update-shapes
|
||||
#{(:id copy-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#aaaaaa")))
|
||||
(:objects page)
|
||||
{})
|
||||
(cls/generate-update-shapes
|
||||
#{(:id main-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{}))
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component1)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
file' (thf/apply-changes updated-file changes2)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#aaaaaa"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') #{:fill-group}))))
|
||||
|
||||
(t/deftest test-sync-when-adding-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]})
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
main-root (ths/get-shape file :main-root)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id main-root)} ; parents
|
||||
(thi/id :main-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
0 ; to-index
|
||||
#{(thi/id :free-shape)}) ; ids
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component1)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
file' (thf/apply-changes updated-file changes2)
|
||||
|
||||
;; ==== Get
|
||||
main-free-shape' (ths/get-shape file' :free-shape)
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-new-child-id' (d/seek #(not= % (thi/id :copy-child)) (:shapes copy-root'))
|
||||
copy-new-child' (ths/get-shape-by-id file' copy-new-child-id')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-new-child'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-new-child') nil))
|
||||
(t/is (ctst/parent-of? copy-root' copy-new-child'))
|
||||
(t/is (ctk/is-main-of? main-free-shape' copy-new-child' true))))
|
||||
|
||||
(t/deftest test-sync-when-deleting-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
|
||||
page (thf/current-page file)
|
||||
main-child (ths/get-shape file :main-child)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action will not
|
||||
;; delete the child shape, but hide it (thus setting the visibility group).
|
||||
[_all-parents changes1]
|
||||
(cls/generate-delete-shapes (pcb/empty-changes)
|
||||
file
|
||||
page
|
||||
(:objects page)
|
||||
#{(:id main-child)}
|
||||
{:components-v2 true})
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component1)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
file' (thf/apply-changes updated-file changes2)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (nil? copy-child'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (empty? (:shapes copy-root')))))
|
||||
|
||||
(t/deftest test-sync-when-moving-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-component-with-many-children-and-copy :component1
|
||||
:main-root
|
||||
[:main-child1 :main-child2 :main-child3]
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child1
|
||||
:copy-child2
|
||||
:copy-child3]})
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
main-child1 (ths/get-shape file :main-child1)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id main-child1)} ; parents
|
||||
(thi/id :main-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
2 ; to-index
|
||||
#{(:id main-child1)}) ; ids
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component1)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
file' (thf/apply-changes updated-file changes2)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child1' (ths/get-shape file' :copy-child1)
|
||||
copy-child2' (ths/get-shape file' :copy-child2)
|
||||
copy-child3' (ths/get-shape file' :copy-child3)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child1'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child1') nil))
|
||||
(t/is (= (:touched copy-child2') nil))
|
||||
(t/is (= (:touched copy-child3') nil))
|
||||
(t/is (= (second (:shapes copy-root')) (:id copy-child1')))
|
||||
(t/is (= (first (:shapes copy-root')) (:id copy-child2')))
|
||||
(t/is (= (nth (:shapes copy-root') 2) (:id copy-child3')))))
|
||||
|
||||
(t/deftest test-sync-when-changing-upper
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:main2-root-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
page (thf/current-page file)
|
||||
main2-root (ths/get-shape file :main2-root)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id main2-root)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component2)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
file' (thf/apply-changes updated-file changes2)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
fills' (:fills copy2-root')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))))
|
||||
|
||||
(t/deftest test-sync-when-changing-lower-near
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:copy2-root-params {:children-labels [:copy2-child]}))
|
||||
page (thf/current-page file)
|
||||
main2-nested-head (ths/get-shape file :main2-nested-head)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id main2-nested-head)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component2)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
file' (thf/apply-changes updated-file changes2)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
copy2-child' (ths/get-shape file' :copy2-child)
|
||||
fills' (:fills copy2-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (some? copy2-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))
|
||||
(t/is (= (:touched copy2-child') nil))))
|
||||
|
||||
(t/deftest test-sync-when-changing-lower-remote
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:copy2-root-params {:children-labels [:copy2-child]}))
|
||||
page (thf/current-page file)
|
||||
main1-root (ths/get-shape file :main1-root)
|
||||
|
||||
;; ==== Action
|
||||
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id main1-root)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
updated-file (thf/apply-changes file changes1)
|
||||
|
||||
changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id updated-file)
|
||||
(thi/id :component1)
|
||||
(:id updated-file)
|
||||
{(:id updated-file) updated-file}
|
||||
(:id updated-file))
|
||||
|
||||
synced-file (thf/apply-changes updated-file changes2)
|
||||
|
||||
changes3 (cll/generate-sync-file-changes (pcb/empty-changes)
|
||||
nil
|
||||
:components
|
||||
(:id synced-file)
|
||||
(thi/id :component2)
|
||||
(:id synced-file)
|
||||
{(:id synced-file) synced-file}
|
||||
(:id synced-file))
|
||||
|
||||
file' (thf/apply-changes synced-file changes3)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
copy2-child' (ths/get-shape file' :copy2-child)
|
||||
fills' (:fills copy2-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (some? copy2-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))
|
||||
(t/is (= (:touched copy2-child') nil))))
|
330
common/test/common_tests/logic/comp_touched_test.cljc
Normal file
330
common/test/common_tests/logic/comp_touched_test.cljc
Normal file
|
@ -0,0 +1,330 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns common-tests.logic.comp-touched-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest test-touched-when-changing-attribute
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:main-child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
page (thf/current-page file)
|
||||
copy-child (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') #{:fill-group}))))
|
||||
|
||||
(t/deftest test-touched-from-library
|
||||
(let [;; ==== Setup
|
||||
library (-> (thf/sample-file :library :is-shared true)
|
||||
(tho/add-simple-component :component1 :main-root :main-child
|
||||
:child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
|
||||
file (-> (thf/sample-file :file)
|
||||
(thc/instantiate-component :component1 :copy-root
|
||||
:library library
|
||||
:children-labels [:copy-child]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-child (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') #{:fill-group}))))
|
||||
|
||||
(t/deftest test-not-touched-when-adding-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]})
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||||
;; will not have any effect, and so the parent shape won't also be touched.
|
||||
changes (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id copy-root)} ; parents
|
||||
(thi/id :copy-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
0 ; to-index
|
||||
#{(thi/id :free-shape)}) ; ids
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-not-touched-when-deleting-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-child (ths/get-shape file :copy-child)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action will not
|
||||
;; delete the child shape, but hide it (thus setting the visibility group).
|
||||
[_all-parents changes]
|
||||
(cls/generate-delete-shapes (pcb/empty-changes)
|
||||
file
|
||||
page
|
||||
(:objects page)
|
||||
#{(:id copy-child)}
|
||||
{:components-v2 true})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape file' :copy-child)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') #{:visibility-group}))))
|
||||
|
||||
(t/deftest test-not-touched-when-moving-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-component-with-many-children-and-copy :component1
|
||||
:main-root
|
||||
[:main-child1 :main-child2 :main-child3]
|
||||
:copy-root
|
||||
:copy-root-params {:children-labels [:copy-child1
|
||||
:copy-child2
|
||||
:copy-child3]})
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-child1 (ths/get-shape file :copy-child1)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||||
;; will not have any effect, and so the parent shape won't also be touched.
|
||||
changes (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id copy-child1)} ; parents
|
||||
(thi/id :copy-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
2 ; to-index
|
||||
#{(:id copy-child1)}) ; ids
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child1' (ths/get-shape file' :copy-child1)
|
||||
copy-child2' (ths/get-shape file' :copy-child2)
|
||||
copy-child3' (ths/get-shape file' :copy-child3)]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy-root'))
|
||||
(t/is (some? copy-child1'))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child1') nil))
|
||||
(t/is (= (:touched copy-child2') nil))
|
||||
(t/is (= (:touched copy-child3') nil))
|
||||
(t/is (= (first (:shapes copy-root')) (:id copy-child1')))
|
||||
(t/is (= (second (:shapes copy-root')) (:id copy-child2')))
|
||||
(t/is (= (nth (:shapes copy-root') 2) (:id copy-child3')))))
|
||||
|
||||
(t/deftest test-touched-when-changing-upper
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:main2-root-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
page (thf/current-page file)
|
||||
copy2-root (ths/get-shape file :copy2-root)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy2-root)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
fills' (:fills copy2-root')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') #{:fill-group}))))
|
||||
|
||||
(t/deftest test-touched-when-changing-lower
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:copy2-root-params {:children-labels [:copy2-child]}))
|
||||
page (thf/current-page file)
|
||||
copy2-child (ths/get-shape file :copy2-child)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy2-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
copy2-child' (ths/get-shape file' :copy2-child)
|
||||
fills' (:fills copy2-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (some? copy2-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))
|
||||
(t/is (= (:touched copy2-child') #{:fill-group}))))
|
||||
|
||||
(t/deftest test-touched-when-changing-lower
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:copy2-root-params {:children-labels [:copy2-child]}))
|
||||
page (thf/current-page file)
|
||||
copy2-child (ths/get-shape file :copy2-child)
|
||||
|
||||
;; ==== Action
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy2-child)}
|
||||
(fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
copy2-child' (ths/get-shape file' :copy2-child)
|
||||
fills' (:fills copy2-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? copy2-root'))
|
||||
(t/is (some? copy2-child'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))
|
||||
(t/is (= (:touched copy2-child') #{:fill-group}))))
|
|
@ -1,47 +0,0 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns common-tests.logic.component-creation-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[clojure.test :as t]
|
||||
[common-tests.helpers.components :as thc]
|
||||
[common-tests.helpers.files :as thf]
|
||||
[common-tests.helpers.ids-map :as thi]
|
||||
[common-tests.helpers.shapes :as ths]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest test-add-component-from-single-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(ths/add-sample-shape :shape1 :type :frame))
|
||||
|
||||
page (thf/current-page file)
|
||||
shape1 (ths/get-shape file :shape1)
|
||||
|
||||
;; ==== Action
|
||||
[_ component-id changes]
|
||||
(cll/generate-add-component (pcb/empty-changes)
|
||||
[shape1]
|
||||
(:objects page)
|
||||
(:id page)
|
||||
(:id file)
|
||||
true
|
||||
nil
|
||||
nil)
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
component (thc/get-component-by-id file' component-id)
|
||||
root (ths/get-shape-by-id file' (:main-instance-id component))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? component))
|
||||
(t/is (some? root))
|
||||
(t/is (= (:component-id root) (:id component)))))
|
|
@ -1,234 +0,0 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns common-tests.logic.components-touched-test
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[clojure.test :as t]
|
||||
[common-tests.helpers.compositions :as tho]
|
||||
[common-tests.helpers.files :as thf]
|
||||
[common-tests.helpers.ids-map :as thi]
|
||||
[common-tests.helpers.shapes :as ths]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest test-touched-when-changing-attribute
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root
|
||||
:main-child-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
|
||||
;; ==== Action
|
||||
update-fn (fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
(:shapes copy-root)
|
||||
update-fn
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))
|
||||
fills' (:fills copy-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') #{:fill-group}))))
|
||||
|
||||
(t/deftest test-not-touched-when-adding-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root)
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||||
;; will not have any effect, and so the parent shape won't also be touched.
|
||||
changes (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id copy-root)} ; parents
|
||||
(thi/id :copy-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
0 ; to-index
|
||||
#{(thi/id :free-shape)}) ; ids
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-touched-when-deleting-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action will not
|
||||
;; delete the child shape, but hide it (thus setting the visibility group).
|
||||
[_all-parents changes]
|
||||
(cls/generate-delete-shapes (pcb/empty-changes)
|
||||
file
|
||||
page
|
||||
(:objects page)
|
||||
(set (:shapes copy-root))
|
||||
{:components-v2 true})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') #{:visibility-group}))))
|
||||
|
||||
(t/deftest test-not-touched-when-moving-shape
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-component-with-many-children-and-copy :component1
|
||||
:main-root
|
||||
[:main-child1 :main-child2 :main-child3]
|
||||
:copy-root)
|
||||
(ths/add-sample-shape :free-shape))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
copy-child1 (ths/get-shape-by-id file (first (:shapes copy-root)))
|
||||
|
||||
;; ==== Action
|
||||
|
||||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||||
;; will not have any effect, and so the parent shape won't also be touched.
|
||||
changes (cls/generate-relocate-shapes (pcb/empty-changes)
|
||||
(:objects page)
|
||||
#{(:parent-id copy-child1)} ; parents
|
||||
(thi/id :copy-root) ; parent-id
|
||||
(:id page) ; page-id
|
||||
2 ; to-index
|
||||
#{(:id copy-child1)}) ; ids
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy-root' (ths/get-shape file' :copy-root)
|
||||
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= (:touched copy-root') nil))
|
||||
(t/is (= (:touched copy-child') nil))))
|
||||
|
||||
(t/deftest test-touched-when-changing-upper
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:root2-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
page (thf/current-page file)
|
||||
copy2-root (ths/get-shape file :copy2-root)
|
||||
|
||||
;; ==== Action
|
||||
update-fn (fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy2-root)}
|
||||
update-fn
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
fills' (:fills copy2-root')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') #{:fill-group}))))
|
||||
|
||||
(t/deftest test-touched-when-changing-lower
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1
|
||||
:main1-root
|
||||
:main1-child
|
||||
:component2
|
||||
:main2-root
|
||||
:main2-nested-head
|
||||
:copy2-root
|
||||
:nested-head-params {:fills (ths/sample-fills-color
|
||||
:fill-color "#abcdef")}))
|
||||
page (thf/current-page file)
|
||||
copy2-root (ths/get-shape file :copy2-root)
|
||||
|
||||
;; ==== Action
|
||||
update-fn (fn [shape]
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||||
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
(:shapes copy2-root)
|
||||
update-fn
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file' (thf/apply-changes file changes)
|
||||
|
||||
;; ==== Get
|
||||
copy2-root' (ths/get-shape file' :copy2-root)
|
||||
copy2-child' (ths/get-shape-by-id file' (first (:shapes copy2-root')))
|
||||
fills' (:fills copy2-child')
|
||||
fill' (first fills')]
|
||||
|
||||
;; ==== Check
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))
|
||||
(t/is (= (:touched copy2-child') #{:fill-group}))))
|
|
@ -8,14 +8,14 @@
|
|||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.file :as ctf]
|
||||
[clojure.test :as t]
|
||||
[common-tests.helpers.components :as thc]
|
||||
[common-tests.helpers.compositions :as tho]
|
||||
[common-tests.helpers.files :as thf]
|
||||
[common-tests.helpers.ids-map :as thi]
|
||||
[common-tests.helpers.shapes :as ths]))
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
(ns common-tests.types.types-libraries-test
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.component :as ctk]
|
||||
|
@ -14,12 +19,7 @@
|
|||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[clojure.test :as t]
|
||||
[common-tests.helpers.components :as thc]
|
||||
[common-tests.helpers.compositions :as tho]
|
||||
[common-tests.helpers.files :as thf]
|
||||
[common-tests.helpers.ids-map :as thi]
|
||||
[common-tests.helpers.shapes :as ths]))
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
|
|
|
@ -18,18 +18,18 @@ export default defineConfig({
|
|||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
workers: process.env.CI ? 4 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://0.0.0.0:3500",
|
||||
baseURL: "http://localhost:3000",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
locale: "en-US"
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
|
@ -42,8 +42,9 @@ export default defineConfig({
|
|||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
timeout: 2 * 60 * 1000,
|
||||
command: "yarn e2e:server",
|
||||
url: "http://0.0.0.0:3500",
|
||||
url: "http://localhost:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
|
|
9
frontend/playwright/data/dashboard/create-project.json
Normal file
9
frontend/playwright/data/dashboard/create-project.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"~:id": "~ue5a24d1b-ef1e-812f-8004-52bab84be6f7",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6",
|
||||
"~:created-at": "~m1715266551088",
|
||||
"~:modified-at": "~m1715266551088",
|
||||
"~:is-default": false,
|
||||
"~:name": "New Project 1",
|
||||
"~:is-pinned": false
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
20
frontend/playwright/data/dashboard/get-project-files.json
Normal file
20
frontend/playwright/data/dashboard/get-project-files.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{
|
||||
"~:id": "~u8b479b80-e02d-8074-8004-4088dc6bfd11",
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1714045521389",
|
||||
"~:modified-at": "~m1714045654874",
|
||||
"~:name": "New File 2",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": false
|
||||
},
|
||||
{
|
||||
"~:id": "~u95d6fdd8-48d8-8148-8004-38af910d2dbe",
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1713518796912",
|
||||
"~:modified-at": "~m1713519762931",
|
||||
"~:name": "New File 1",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": false
|
||||
}
|
||||
]
|
18
frontend/playwright/data/dashboard/get-projects-new.json
Normal file
18
frontend/playwright/data/dashboard/get-projects-new.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
[{
|
||||
"~:id": "~ue5a24d1b-ef1e-812f-8004-52bab84be6f7",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1715266551088",
|
||||
"~:modified-at": "~m1715266551088",
|
||||
"~:is-default": false,
|
||||
"~:name": "New Project 1",
|
||||
"~:is-pinned": false,
|
||||
"~:count": 0
|
||||
},
|
||||
{
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1713533116382",
|
||||
"~:modified-at": "~m1713873823633",
|
||||
"~:is-default": true,
|
||||
"~:name": "Drafts"
|
||||
}]
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"~:email": "foo@example.com",
|
||||
"~:is-demo": false,
|
||||
"~:auth-backend": "penpot",
|
||||
"~:fullname": "Princesa Leia",
|
||||
"~:modified-at": "~m1713533116365",
|
||||
"~:is-active": true,
|
||||
"~:default-project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:is-muted": false,
|
||||
"~:default-team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1713533116365",
|
||||
"~:is-blocked": false,
|
||||
"~:props": {
|
||||
"~:nudge": {
|
||||
"~:big": 10,
|
||||
"~:small": 1
|
||||
},
|
||||
"~:v2-info-shown": true,
|
||||
"~:viewed-tutorial?": false,
|
||||
"~:viewed-walkthrough?": false,
|
||||
"~:onboarding-viewed": true,
|
||||
"~:builtin-templates-collapsed-status":
|
||||
true
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
58
frontend/playwright/data/workspace/get-file-blank.json
Normal file
58
frontend/playwright/data/workspace/get-file-blank.json
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true,
|
||||
"~:can-read": true,
|
||||
"~:is-logged": true
|
||||
},
|
||||
"~:has-media-trimmed": false,
|
||||
"~:comment-thread-seqn": 0,
|
||||
"~:name": "New File 1",
|
||||
"~:revn": 11,
|
||||
"~:modified-at": "~m1713873823633",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:is-shared": false,
|
||||
"~:version": 46,
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1713536343369",
|
||||
"~:data": {
|
||||
"~:pages": [
|
||||
"~uc7ce0794-0992-8105-8004-38f28044384a"
|
||||
],
|
||||
"~:pages-index": {
|
||||
"~uc7ce0794-0992-8105-8004-38f28044384a": {
|
||||
"~#penpot/pointer": [
|
||||
"~ude58c8f6-c5c2-8196-8004-3df9e2e52d88",
|
||||
{
|
||||
"~:created-at": "~m1713873823636"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:options": {
|
||||
"~:components-v2": true
|
||||
},
|
||||
"~:recent-colors": [
|
||||
{
|
||||
"~:color": "#0000ff",
|
||||
"~:opacity": 1,
|
||||
"~:id": null,
|
||||
"~:file-id": null,
|
||||
"~:image": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
{
|
||||
"~:id": "~ude58c8f6-c5c2-8196-8004-3df9e2e52d88",
|
||||
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:created-at": "~m1713873823631",
|
||||
"~:content": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
"~#shape": {
|
||||
"~:y": 0,
|
||||
"~:hide-fill-on-export": false,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:name": "Root Frame",
|
||||
"~:width": 0.01,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 0.01
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [],
|
||||
"~:x": 0,
|
||||
"~:proportion": 1,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 0,
|
||||
"~:y": 0,
|
||||
"~:width": 0.01,
|
||||
"~:height": 0.01,
|
||||
"~:x1": 0,
|
||||
"~:y1": 0,
|
||||
"~:x2": 0.01,
|
||||
"~:y2": 0.01
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#FFFFFF",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 0.01,
|
||||
"~:flip-y": null,
|
||||
"~:shapes": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f28044384a",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"c7ce0794-0992-8105-8004-38f280443849/c7ce0794-0992-8105-8004-38f28044384a/8c1035fa-01f0-8071-8004-3df966ff2c64/frame": "http://localhost:3449/assets/by-id/50d097ed-d321-4319-b00b-e82a9c9435ea"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:email": "foo@example.com",
|
||||
"~:name": "Princesa Leia",
|
||||
"~:fullname": "Princesa Leia",
|
||||
"~:is-active": true
|
||||
}
|
||||
]
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1713533116382",
|
||||
"~:modified-at": "~m1713873823633",
|
||||
"~:is-default": true,
|
||||
"~:name": "Drafts"
|
||||
}
|
23
frontend/playwright/data/workspace/get-team-default.json
Normal file
23
frontend/playwright/data/workspace/get-team-default.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true
|
||||
},
|
||||
"~:name": "Default",
|
||||
"~:modified-at": "~m1713533116375",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1713533116375",
|
||||
"~:is-default": true
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"~:id": "~u088df3d4-d383-80f6-8004-527e50ea4f1f",
|
||||
"~:revn": 21,
|
||||
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:session-id": "~u1dc6d4fa-7bd3-803a-8004-527dd9df2c62",
|
||||
"~:changes": []
|
||||
}
|
||||
]
|
7
frontend/playwright/data/workspace/ws-notifications.js
Normal file
7
frontend/playwright/data/workspace/ws-notifications.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export const presenceFixture = {
|
||||
"~:type": "~:presence",
|
||||
"~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:session-id": "~u37730924-d520-80f1-8004-4ae6e5c3942d",
|
||||
"~:profile-id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:subs-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
};
|
74
frontend/playwright/helpers/MockWebSocketHelper.js
Normal file
74
frontend/playwright/helpers/MockWebSocketHelper.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
export class MockWebSocketHelper extends EventTarget {
|
||||
static #mocks = new Map();
|
||||
|
||||
static async init(page) {
|
||||
await page.exposeFunction("MockWebSocket$$constructor", (url, protocols) => {
|
||||
const webSocket = new MockWebSocketHelper(page, url, protocols);
|
||||
this.#mocks.set(url, webSocket);
|
||||
});
|
||||
await page.exposeFunction("MockWebSocket$$spyMessage", (url, data) => {
|
||||
if (!this.#mocks.has(url)) {
|
||||
throw new Error(`WebSocket with URL ${url} not found`);
|
||||
}
|
||||
this.#mocks.get(url).dispatchEvent(new MessageEvent("message", { data }));
|
||||
});
|
||||
await page.exposeFunction("MockWebSocket$$spyClose", (url, code, reason) => {
|
||||
if (!this.#mocks.has(url)) {
|
||||
throw new Error(`WebSocket with URL ${url} not found`);
|
||||
}
|
||||
this.#mocks.get(url).dispatchEvent(new CloseEvent("close", { code, reason }));
|
||||
});
|
||||
await page.addInitScript({ path: "playwright/scripts/MockWebSocket.js" });
|
||||
}
|
||||
|
||||
static waitForURL(url) {
|
||||
return new Promise((resolve) => {
|
||||
const intervalID = setInterval(() => {
|
||||
for (const [wsURL, ws] of this.#mocks) {
|
||||
if (wsURL.includes(url)) {
|
||||
clearInterval(intervalID);
|
||||
return resolve(ws);
|
||||
}
|
||||
}
|
||||
}, 30);
|
||||
});
|
||||
}
|
||||
|
||||
#page = null;
|
||||
#url;
|
||||
#protocols;
|
||||
|
||||
constructor(page, url, protocols) {
|
||||
super();
|
||||
this.#page = page;
|
||||
this.#url = url;
|
||||
this.#protocols = protocols;
|
||||
}
|
||||
|
||||
mockOpen(options) {
|
||||
return this.#page.evaluate(({ url, options }) => {
|
||||
if (typeof WebSocket.getByURL !== 'function') {
|
||||
throw new Error('WebSocket.getByURL is not a function. Did you forget to call MockWebSocket.init(page)?')
|
||||
}
|
||||
WebSocket.getByURL(url).mockOpen(options);
|
||||
}, { url: this.#url, options });
|
||||
}
|
||||
|
||||
mockMessage(data) {
|
||||
return this.#page.evaluate(({ url, data }) => {
|
||||
if (typeof WebSocket.getByURL !== 'function') {
|
||||
throw new Error('WebSocket.getByURL is not a function. Did you forget to call MockWebSocket.init(page)?')
|
||||
}
|
||||
WebSocket.getByURL(url).mockMessage(data);
|
||||
}, { url: this.#url, data });
|
||||
}
|
||||
|
||||
mockClose() {
|
||||
return this.#page.evaluate(({ url }) => {
|
||||
if (typeof WebSocket.getByURL !== 'function') {
|
||||
throw new Error('WebSocket.getByURL is not a function. Did you forget to call MockWebSocket.init(page)?')
|
||||
}
|
||||
WebSocket.getByURL(url).mockClose();
|
||||
}, { url: this.#url });
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
export const interceptRPC = async (page, path, jsonFilename, options = {}) => {
|
||||
const interceptConfig = {
|
||||
status: 200,
|
||||
...options,
|
||||
};
|
||||
|
||||
await page.route(`**/api/rpc/command/${path}`, async (route) => {
|
||||
await route.fulfill({
|
||||
...interceptConfig,
|
||||
contentType: "application/transit+json",
|
||||
path: `playwright/data/${jsonFilename}`,
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
import { interceptRPC } from "./index";
|
||||
|
||||
|
||||
export const setupNotLogedIn = async (page) => {
|
||||
await interceptRPC(page, "get-profile", "get-profile-anonymous.json");
|
||||
|
||||
};
|
||||
|
226
frontend/playwright/scripts/MockWebSocket.js
Normal file
226
frontend/playwright/scripts/MockWebSocket.js
Normal file
|
@ -0,0 +1,226 @@
|
|||
window.WebSocket = class MockWebSocket extends EventTarget {
|
||||
static CONNECTING = 0;
|
||||
static OPEN = 1;
|
||||
static CLOSING = 2;
|
||||
static CLOSED = 3;
|
||||
|
||||
static #mocks = new Map();
|
||||
|
||||
static getAll() {
|
||||
return this.#mocks.values();
|
||||
}
|
||||
|
||||
static getByURL(url) {
|
||||
if (this.#mocks.has(url)) {
|
||||
return this.#mocks.get(url);
|
||||
}
|
||||
for (const [wsURL, ws] of this.#mocks) {
|
||||
if (wsURL.includes(url)) {
|
||||
return ws;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
#url;
|
||||
#protocols;
|
||||
#protocol = "";
|
||||
#binaryType = "blob";
|
||||
#bufferedAmount = 0;
|
||||
#extensions = "";
|
||||
#readyState = MockWebSocket.CONNECTING;
|
||||
|
||||
#onopen = null;
|
||||
#onerror = null;
|
||||
#onmessage = null;
|
||||
#onclose = null;
|
||||
|
||||
#spyMessage = null;
|
||||
#spyClose = null;
|
||||
|
||||
constructor(url, protocols) {
|
||||
super();
|
||||
|
||||
this.#url = url;
|
||||
this.#protocols = protocols || [];
|
||||
|
||||
MockWebSocket.#mocks.set(this.#url, this);
|
||||
|
||||
if (typeof window["MockWebSocket$$constructor"] === "function") {
|
||||
MockWebSocket$$constructor(this.#url, this.#protocols);
|
||||
}
|
||||
if (typeof window["MockWebSocket$$spyMessage"] === "function") {
|
||||
this.#spyMessage = MockWebSocket$$spyMessage;
|
||||
}
|
||||
if (typeof window["MockWebSocket$$spyClose"] === "function") {
|
||||
this.#spyClose = MockWebSocket$$spyClose;
|
||||
}
|
||||
}
|
||||
|
||||
set binaryType(binaryType) {
|
||||
if (!["blob", "arraybuffer"].includes(binaryType)) {
|
||||
return;
|
||||
}
|
||||
this.#binaryType = binaryType;
|
||||
}
|
||||
|
||||
get binaryType() {
|
||||
return this.#binaryType;
|
||||
}
|
||||
|
||||
get bufferedAmount() {
|
||||
return this.#bufferedAmount;
|
||||
}
|
||||
|
||||
get extensions() {
|
||||
return this.#extensions;
|
||||
}
|
||||
|
||||
get readyState() {
|
||||
return this.#readyState;
|
||||
}
|
||||
|
||||
get protocol() {
|
||||
return this.#protocol;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.#url;
|
||||
}
|
||||
|
||||
set onopen(callback) {
|
||||
this.removeEventListener("open", this.#onopen);
|
||||
this.#onopen = null;
|
||||
|
||||
if (typeof callback === "function") {
|
||||
this.addEventListener("open", callback);
|
||||
this.#onopen = callback;
|
||||
}
|
||||
}
|
||||
|
||||
get onopen() {
|
||||
return this.#onopen;
|
||||
}
|
||||
|
||||
set onerror(callback) {
|
||||
this.removeEventListener("error", this.#onerror);
|
||||
this.#onerror = null;
|
||||
|
||||
if (typeof callback === "function") {
|
||||
this.addEventListener("error", callback);
|
||||
this.#onerror = callback;
|
||||
}
|
||||
}
|
||||
|
||||
get onerror() {
|
||||
return this.#onerror;
|
||||
}
|
||||
|
||||
set onmessage(callback) {
|
||||
this.removeEventListener("message", this.#onmessage);
|
||||
this.#onmessage = null;
|
||||
|
||||
if (typeof callback === "function") {
|
||||
this.addEventListener("message", callback);
|
||||
this.#onmessage = callback;
|
||||
}
|
||||
}
|
||||
|
||||
get onmessage() {
|
||||
return this.#onmessage;
|
||||
}
|
||||
|
||||
set onclose(callback) {
|
||||
this.removeEventListener("close", this.#onclose);
|
||||
this.#onclose = null;
|
||||
|
||||
if (typeof callback === "function") {
|
||||
this.addEventListener("close", callback);
|
||||
this.#onclose = callback;
|
||||
}
|
||||
}
|
||||
|
||||
get onclose() {
|
||||
return this.#onclose;
|
||||
}
|
||||
|
||||
get mockProtocols() {
|
||||
return this.#protocols;
|
||||
}
|
||||
|
||||
spyClose(callback) {
|
||||
if (typeof callback !== "function") {
|
||||
throw new TypeError("Invalid callback");
|
||||
}
|
||||
this.#spyClose = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
spyMessage(callback) {
|
||||
if (typeof callback !== "function") {
|
||||
throw new TypeError("Invalid callback");
|
||||
}
|
||||
this.#spyMessage = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
mockOpen(options) {
|
||||
this.#protocol = options?.protocol || "";
|
||||
this.#extensions = options?.extensions || "";
|
||||
this.#readyState = MockWebSocket.OPEN;
|
||||
this.dispatchEvent(new Event("open"));
|
||||
return this;
|
||||
}
|
||||
|
||||
mockError(error) {
|
||||
this.#readyState = MockWebSocket.CLOSED;
|
||||
this.dispatchEvent(new ErrorEvent("error", { error }));
|
||||
return this;
|
||||
}
|
||||
|
||||
mockMessage(data) {
|
||||
if (this.#readyState !== MockWebSocket.OPEN) {
|
||||
throw new Error("MockWebSocket is not connected");
|
||||
}
|
||||
this.dispatchEvent(new MessageEvent("message", { data }));
|
||||
return this;
|
||||
}
|
||||
|
||||
mockClose(code, reason) {
|
||||
this.#readyState = MockWebSocket.CLOSED;
|
||||
this.dispatchEvent(new CloseEvent("close", { code: code || 1000, reason: reason || "" }));
|
||||
return this;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
if (this.#readyState === MockWebSocket.CONNECTING) {
|
||||
throw new DOMException("InvalidStateError", "MockWebSocket is not connected");
|
||||
}
|
||||
|
||||
if (this.#spyMessage) {
|
||||
this.#spyMessage(this.url, data);
|
||||
}
|
||||
}
|
||||
|
||||
close(code, reason) {
|
||||
if (code && !Number.isInteger(code) && code !== 1000 && (code < 3000 || code > 4999)) {
|
||||
throw new DOMException("InvalidAccessError", "Invalid code");
|
||||
}
|
||||
|
||||
if (reason && typeof reason === "string") {
|
||||
const reasonBytes = new TextEncoder().encode(reason);
|
||||
if (reasonBytes.length > 123) {
|
||||
throw new DOMException("SyntaxError", "Reason is too long");
|
||||
}
|
||||
}
|
||||
|
||||
if ([MockWebSocket.CLOSED, MockWebSocket.CLOSING].includes(this.#readyState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#readyState = MockWebSocket.CLOSING;
|
||||
if (this.#spyClose) {
|
||||
this.#spyClose(this.url, code, reason);
|
||||
}
|
||||
}
|
||||
};
|
39
frontend/playwright/ui/pages/BasePage.js
Normal file
39
frontend/playwright/ui/pages/BasePage.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
export class BasePage {
|
||||
static async mockRPC(page, path, jsonFilename, options) {
|
||||
if (!page) {
|
||||
throw new TypeError("Invalid page argument. Must be a Playwright page.");
|
||||
}
|
||||
if (typeof path !== "string" && !(path instanceof RegExp)) {
|
||||
throw new TypeError("Invalid path argument. Must be a string or a RegExp.");
|
||||
}
|
||||
|
||||
const url = typeof path === "string" ? `**/api/rpc/command/${path}` : path;
|
||||
const interceptConfig = {
|
||||
status: 200,
|
||||
contentType: "application/transit+json",
|
||||
...options,
|
||||
};
|
||||
return page.route(url, (route) =>
|
||||
route.fulfill({
|
||||
...interceptConfig,
|
||||
path: `playwright/data/${jsonFilename}`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#page = null;
|
||||
|
||||
constructor(page) {
|
||||
this.#page = page;
|
||||
}
|
||||
|
||||
get page() {
|
||||
return this.#page;
|
||||
}
|
||||
|
||||
async mockRPC(path, jsonFilename, options) {
|
||||
return BasePage.mockRPC(this.page, path, jsonFilename, options);
|
||||
}
|
||||
}
|
||||
|
||||
export default BasePage;
|
32
frontend/playwright/ui/pages/BaseWebSocketPage.js
Normal file
32
frontend/playwright/ui/pages/BaseWebSocketPage.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { MockWebSocketHelper } from "../../helpers/MockWebSocketHelper";
|
||||
import BasePage from "./BasePage";
|
||||
|
||||
export class BaseWebSocketPage extends BasePage {
|
||||
/**
|
||||
* This should be called on `test.beforeEach`.
|
||||
*
|
||||
* @param {Page} page
|
||||
* @returns
|
||||
*/
|
||||
static initWebSockets(page) {
|
||||
return MockWebSocketHelper.init(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves when a WebSocket with the given URL is created.
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {Promise<MockWebSocketHelper>}
|
||||
*/
|
||||
async waitForWebSocket(url) {
|
||||
return MockWebSocketHelper.waitForURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<MockWebSocketHelper>}
|
||||
*/
|
||||
async waitForNotificationsWebSocket() {
|
||||
return this.waitForWebSocket("ws://localhost:3000/ws/notifications");
|
||||
}
|
||||
}
|
93
frontend/playwright/ui/pages/DashboardPage.js
Normal file
93
frontend/playwright/ui/pages/DashboardPage.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { BaseWebSocketPage } from "./BaseWebSocketPage";
|
||||
|
||||
export class DashboardPage extends BaseWebSocketPage {
|
||||
static async init(page) {
|
||||
await BaseWebSocketPage.initWebSockets(page);
|
||||
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-profile",
|
||||
"logged-in-user/get-profile-logged-in-no-onboarding.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "logged-in-user/get-teams-default.json");
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-font-variants?team-id=*",
|
||||
"workspace/get-font-variants-empty.json",
|
||||
);
|
||||
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-projects?team-id=*",
|
||||
"logged-in-user/get-projects-default.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-team-members?team-id=*",
|
||||
"logged-in-user/get-team-members-your-penpot.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-team-users?team-id=*",
|
||||
"logged-in-user/get-team-users-single-user.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-unread-comment-threads?team-id=*",
|
||||
"logged-in-user/get-team-users-single-user.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-team-recent-files?team-id=*",
|
||||
"logged-in-user/get-team-recent-files-empty.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-profiles-for-file-comments",
|
||||
"workspace/get-profile-for-file-comments.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-builtin-templates",
|
||||
"logged-in-user/get-built-in-templates-empty.json",
|
||||
);
|
||||
}
|
||||
|
||||
static anyTeamId = "c7ce0794-0992-8105-8004-38e630f40f6d";
|
||||
|
||||
static draftProjectId = "c7ce0794-0992-8105-8004-38e630f7920b";
|
||||
|
||||
constructor(page) {
|
||||
super(page);
|
||||
this.titleLabel = page.getByRole("heading", { name: "Projects" });
|
||||
this.addProjectBtn = page.getByRole("button", { name: "+ NEW PROJECT" });
|
||||
this.projectName = page.getByText("Project 1");
|
||||
this.draftTitle = page.getByRole("heading", { name: "Drafts" });
|
||||
this.draftLink = page.getByTestId("drafts-link-sidebar");
|
||||
this.draftsFile = page.getByText(/New File 1/);
|
||||
}
|
||||
|
||||
async setupDraftsEmpty() {
|
||||
await this.mockRPC("get-project-files?project-id=*", "dashboard/get-project-files-empty.json");
|
||||
}
|
||||
|
||||
async setupDrafts() {
|
||||
await this.mockRPC("get-project-files?project-id=*", "dashboard/get-project-files.json");
|
||||
}
|
||||
|
||||
async setupNewProject() {
|
||||
await this.mockRPC("create-project", "dashboard/create-project.json", { method: "POST" });
|
||||
await this.mockRPC("get-projects?team-id=*", "dashboard/get-projects-new.json");
|
||||
}
|
||||
async goToWorkspace() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.anyTeamId}/projects`);
|
||||
}
|
||||
|
||||
async goToDrafts() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.anyTeamId}/projects/${DashboardPage.draftProjectId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DashboardPage;
|
54
frontend/playwright/ui/pages/LoginPage.js
Normal file
54
frontend/playwright/ui/pages/LoginPage.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { BasePage } from "./BasePage";
|
||||
|
||||
export class LoginPage extends BasePage {
|
||||
static async initWithLoggedOutUser(page) {
|
||||
await BasePage.mockRPC(page, "get-profile", "get-profile-anonymous.json");
|
||||
}
|
||||
|
||||
constructor(page) {
|
||||
super(page);
|
||||
this.loginButton = page.getByRole("button", { name: "Login" });
|
||||
this.password = page.getByLabel("Password");
|
||||
this.userName = page.getByLabel("Email");
|
||||
this.invalidCredentialsError = page.getByText("Email or password is incorrect");
|
||||
this.invalidEmailError = page.getByText("Enter a valid email please");
|
||||
this.initialHeading = page.getByRole("heading", { name: "Log into my account" });
|
||||
}
|
||||
|
||||
async fillEmailAndPasswordInputs(email, password) {
|
||||
await this.userName.fill(email);
|
||||
await this.password.fill(password);
|
||||
}
|
||||
|
||||
async clickLoginButton() {
|
||||
await this.loginButton.click();
|
||||
}
|
||||
|
||||
async setupLoggedInUser() {
|
||||
await this.mockRPC("get-profile", "logged-in-user/get-profile-logged-in.json");
|
||||
await this.mockRPC("get-teams", "logged-in-user/get-teams-default.json");
|
||||
await this.mockRPC("get-font-variants?team-id=*", "logged-in-user/get-font-variants-empty.json");
|
||||
await this.mockRPC("get-projects?team-id=*", "logged-in-user/get-projects-default.json");
|
||||
await this.mockRPC("get-team-members?team-id=*", "logged-in-user/get-team-members-your-penpot.json");
|
||||
await this.mockRPC("get-team-users?team-id=*", "logged-in-user/get-team-users-single-user.json");
|
||||
await this.mockRPC(
|
||||
"get-unread-comment-threads?team-id=*",
|
||||
"logged-in-user/get-team-users-single-user.json",
|
||||
);
|
||||
await this.mockRPC("get-team-recent-files?team-id=*", "logged-in-user/get-team-recent-files-empty.json");
|
||||
await this.mockRPC(
|
||||
"get-profiles-for-file-comments",
|
||||
"logged-in-user/get-profiles-for-file-comments-empty.json",
|
||||
);
|
||||
}
|
||||
|
||||
async setupLoginSuccess() {
|
||||
await this.mockRPC("login-with-password", "logged-in-user/login-with-password-success.json");
|
||||
}
|
||||
|
||||
async setupLoginError() {
|
||||
await this.mockRPC("login-with-password", "login-with-password-error.json", { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
export default LoginPage;
|
99
frontend/playwright/ui/pages/WorkspacePage.js
Normal file
99
frontend/playwright/ui/pages/WorkspacePage.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { expect } from "@playwright/test";
|
||||
import { BaseWebSocketPage } from "./BaseWebSocketPage";
|
||||
|
||||
export class WorkspacePage extends BaseWebSocketPage {
|
||||
/**
|
||||
* This should be called on `test.beforeEach`.
|
||||
*
|
||||
* @param {Page} page
|
||||
* @returns
|
||||
*/
|
||||
static async init(page) {
|
||||
await BaseWebSocketPage.initWebSockets(page);
|
||||
|
||||
await BaseWebSocketPage.mockRPC(page, "get-profile", "logged-in-user/get-profile-logged-in.json");
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-team-users?file-id=*",
|
||||
"logged-in-user/get-team-users-single-user.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-comment-threads?file-id=*",
|
||||
"workspace/get-comment-threads-empty.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-project?id=*", "workspace/get-project-default.json");
|
||||
await BaseWebSocketPage.mockRPC(page, "get-team?id=*", "workspace/get-team-default.json");
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-profiles-for-file-comments?file-id=*",
|
||||
"workspace/get-profile-for-file-comments.json",
|
||||
);
|
||||
}
|
||||
|
||||
static anyProjectId = "c7ce0794-0992-8105-8004-38e630f7920b";
|
||||
static anyFileId = "c7ce0794-0992-8105-8004-38f280443849";
|
||||
static anyPageId = "c7ce0794-0992-8105-8004-38f28044384a";
|
||||
|
||||
#ws = null;
|
||||
|
||||
constructor(page) {
|
||||
super(page);
|
||||
this.pageName = page.getByTestId("page-name");
|
||||
this.presentUserListItems = page.getByTestId("active-users-list").getByAltText("Princesa Leia");
|
||||
this.viewport = page.getByTestId("viewport");
|
||||
this.rootShape = page.locator(`[id="shape-00000000-0000-0000-0000-000000000000"]`);
|
||||
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
|
||||
}
|
||||
|
||||
async goToWorkspace() {
|
||||
await this.page.goto(
|
||||
`/#/workspace/${WorkspacePage.anyProjectId}/${WorkspacePage.anyFileId}?page-id=${WorkspacePage.anyPageId}`,
|
||||
);
|
||||
|
||||
this.#ws = await this.waitForNotificationsWebSocket();
|
||||
await this.#ws.mockOpen();
|
||||
await this.#waitForWebSocketReadiness();
|
||||
}
|
||||
|
||||
async #waitForWebSocketReadiness() {
|
||||
// TODO: find a better event to settle whether the app is ready to receive notifications via ws
|
||||
await expect(this.pageName).toHaveText("Page 1");
|
||||
}
|
||||
|
||||
async sendPresenceMessage(fixture) {
|
||||
await this.#ws.mockMessage(JSON.stringify(fixture));
|
||||
}
|
||||
|
||||
async cleanUp() {
|
||||
await this.#ws.mockClose();
|
||||
}
|
||||
|
||||
async setupEmptyFile() {
|
||||
await this.mockRPC("get-profile", "logged-in-user/get-profile-logged-in.json");
|
||||
await this.mockRPC("get-team-users?file-id=*", "logged-in-user/get-team-users-single-user.json");
|
||||
await this.mockRPC("get-comment-threads?file-id=*", "workspace/get-comment-threads-empty.json");
|
||||
await this.mockRPC("get-project?id=*", "workspace/get-project-default.json");
|
||||
await this.mockRPC("get-team?id=*", "workspace/get-team-default.json");
|
||||
await this.mockRPC(
|
||||
"get-profiles-for-file-comments?file-id=*",
|
||||
"workspace/get-profile-for-file-comments.json",
|
||||
);
|
||||
await this.mockRPC(/get\-file\?/, "workspace/get-file-blank.json");
|
||||
await this.mockRPC(
|
||||
"get-file-object-thumbnails?file-id=*",
|
||||
"workspace/get-file-object-thumbnails-blank.json",
|
||||
);
|
||||
await this.mockRPC("get-font-variants?team-id=*", "workspace/get-font-variants-empty.json");
|
||||
await this.mockRPC("get-file-fragment?file-id=*", "workspace/get-file-fragment-blank.json");
|
||||
await this.mockRPC("get-file-libraries?file-id=*", "workspace/get-file-libraries-empty.json");
|
||||
}
|
||||
|
||||
async clickWithDragViewportAt(x, y, width, height) {
|
||||
await this.page.waitForTimeout(100);
|
||||
await this.viewport.hover({ position: { x, y } });
|
||||
await this.page.mouse.down();
|
||||
await this.viewport.hover({ position: { x: x + width, y: y + height } });
|
||||
await this.page.mouse.up();
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
import { interceptRPC } from "../../helpers/index";
|
||||
|
||||
class LoginPage {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.loginButton = page.getByRole("button", { name: "Login" });
|
||||
this.password = page.getByLabel("Password");
|
||||
this.userName = page.getByLabel("Email");
|
||||
this.message = page.getByText("Email or password is incorrect");
|
||||
this.badLoginMsg = page.getByText("Enter a valid email please");
|
||||
this.initialHeading = page.getByRole("heading", { name: "Log into my account" });
|
||||
}
|
||||
|
||||
url() {
|
||||
return this.page.url();
|
||||
}
|
||||
|
||||
context() {
|
||||
return this.page.context();
|
||||
}
|
||||
|
||||
async fillEmailAndPasswordInputs(email, password) {
|
||||
await this.userName.fill(email);
|
||||
await this.password.fill(password);
|
||||
}
|
||||
|
||||
async clickLoginButton() {
|
||||
await this.loginButton.click();
|
||||
}
|
||||
|
||||
async setupAllowedUser() {
|
||||
await interceptRPC(this.page, "get-profile", "logged-in-user/get-profile-logged-in.json");
|
||||
await interceptRPC(this.page, "get-teams", "logged-in-user/get-teams-default.json");
|
||||
await interceptRPC(
|
||||
this.page,
|
||||
"get-font-variants?team-id=*",
|
||||
"logged-in-user/get-font-variants-empty.json",
|
||||
);
|
||||
await interceptRPC(this.page, "get-projects?team-id=*", "logged-in-user/get-projects-default.json");
|
||||
await interceptRPC(
|
||||
this.page,
|
||||
"get-team-members?team-id=*",
|
||||
"logged-in-user/get-team-members-your-penpot.json",
|
||||
);
|
||||
await interceptRPC(
|
||||
this.page,
|
||||
"get-team-users?team-id=*",
|
||||
"logged-in-user/get-team-users-single-user.json",
|
||||
);
|
||||
await interceptRPC(
|
||||
this.page,
|
||||
"get-unread-comment-threads?team-id=*",
|
||||
"logged-in-user/get-team-users-single-user.json",
|
||||
);
|
||||
await interceptRPC(
|
||||
this.page,
|
||||
"get-team-recent-files?team-id=*",
|
||||
"logged-in-user/get-team-recent-files-empty.json",
|
||||
);
|
||||
await interceptRPC(
|
||||
this.page,
|
||||
"get-profiles-for-file-comments",
|
||||
"logged-in-user/get-profiles-for-file-comments-empty.json",
|
||||
);
|
||||
}
|
||||
|
||||
async setupLoginSuccess() {
|
||||
await interceptRPC(this.page, "login-with-password", "logged-in-user/login-with-password-success.json");
|
||||
}
|
||||
|
||||
async setupLoginError() {
|
||||
await interceptRPC(this.page, "login-with-password", "login-with-password-error.json", { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
export default LoginPage;
|
44
frontend/playwright/ui/specs/dashboard.spec.js
Normal file
44
frontend/playwright/ui/specs/dashboard.spec.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import DashboardPage from "../pages/DashboardPage";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await DashboardPage.init(page);
|
||||
});
|
||||
|
||||
test("Dashboad page has title ", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
|
||||
await expect(dashboardPage.page).toHaveURL(/dashboard/);
|
||||
await expect(dashboardPage.titleLabel).toBeVisible();
|
||||
});
|
||||
|
||||
test("User can create a new project", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupNewProject();
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
await dashboardPage.addProjectBtn.click();
|
||||
|
||||
await expect(dashboardPage.projectName).toBeVisible();
|
||||
});
|
||||
|
||||
test("User goes to draft page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDraftsEmpty();
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
await dashboardPage.draftLink.click();
|
||||
|
||||
await expect(dashboardPage.draftTitle).toBeVisible();
|
||||
});
|
||||
|
||||
test("User loads the draft page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDrafts();
|
||||
|
||||
await dashboardPage.goToDrafts();
|
||||
|
||||
await expect(dashboardPage.draftsFile).toBeVisible();
|
||||
});
|
|
@ -1,54 +1,50 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { setupNotLogedIn } from "../../helpers/intercepts";
|
||||
|
||||
import LoginPage from "../pages/login-page";
|
||||
import { LoginPage } from "../pages/LoginPage";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupNotLogedIn(page);
|
||||
await LoginPage.initWithLoggedOutUser(page);
|
||||
await page.goto("/#/auth/login");
|
||||
});
|
||||
|
||||
test("Shows login page when going to index and user is logged out", async ({ page }) => {
|
||||
test("User is redirected to the login page when logged out", async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
await loginPage.setupAllowedUser();
|
||||
await loginPage.setupLoggedInUser();
|
||||
|
||||
await expect(loginPage.url()).toMatch(/auth\/login$/);
|
||||
await expect(loginPage.page).toHaveURL(/auth\/login$/);
|
||||
await expect(loginPage.initialHeading).toBeVisible();
|
||||
});
|
||||
|
||||
test("User submit a wrong formated email ", async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
test.describe("Login form", () => {
|
||||
test("User logs in by filling the login form", async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.setupLoginSuccess();
|
||||
await loginPage.setupLoggedInUser();
|
||||
|
||||
await loginPage.setupLoginSuccess();
|
||||
await loginPage.fillEmailAndPasswordInputs("foo@example.com", "loremipsum");
|
||||
await loginPage.clickLoginButton();
|
||||
|
||||
await loginPage.fillEmailAndPasswordInputs("foo", "lorenIpsum");
|
||||
await page.waitForURL("**/dashboard/**");
|
||||
await expect(loginPage.page).toHaveURL(/dashboard/);
|
||||
});
|
||||
|
||||
await expect(loginPage.badLoginMsg).toBeVisible();
|
||||
});
|
||||
|
||||
test("User logs in by filling the login form", async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
await loginPage.setupLoginSuccess();
|
||||
await loginPage.setupAllowedUser();
|
||||
|
||||
await loginPage.fillEmailAndPasswordInputs("foo@example.com", "loremipsum");
|
||||
await loginPage.clickLoginButton();
|
||||
|
||||
await page.waitForURL('**/dashboard/**');
|
||||
await expect(page).toHaveURL(/dashboard/);
|
||||
// await expect(loginPage.url()).toMatch(/dashboard/);
|
||||
});
|
||||
|
||||
test("User submits wrong credentials", async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
await loginPage.setupLoginError();
|
||||
|
||||
await loginPage.fillEmailAndPasswordInputs("test@example.com", "loremipsum");
|
||||
await loginPage.clickLoginButton();
|
||||
|
||||
await expect(loginPage.message).toBeVisible();
|
||||
await expect(loginPage.url()).toMatch(/auth\/login$/);
|
||||
test("User gets error message when submitting an bad formatted email ", async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.setupLoginSuccess();
|
||||
|
||||
await loginPage.fillEmailAndPasswordInputs("foo", "lorenIpsum");
|
||||
|
||||
await expect(loginPage.invalidEmailError).toBeVisible();
|
||||
});
|
||||
|
||||
test("User gets error message when submitting wrong credentials", async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.setupLoginError();
|
||||
|
||||
await loginPage.fillEmailAndPasswordInputs("test@example.com", "loremipsum");
|
||||
await loginPage.clickLoginButton();
|
||||
|
||||
await expect(loginPage.invalidCredentialsError).toBeVisible();
|
||||
await expect(loginPage.page).toHaveURL(/auth\/login$/);
|
||||
});
|
||||
});
|
||||
|
|
40
frontend/playwright/ui/specs/workspace.spec.js
Normal file
40
frontend/playwright/ui/specs/workspace.spec.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../pages/WorkspacePage";
|
||||
import { presenceFixture } from "../../data/workspace/ws-notifications";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
});
|
||||
|
||||
test.skip("User loads worskpace with empty file", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await expect(workspacePage.pageName).toHaveText("Page 1");
|
||||
});
|
||||
|
||||
test.skip("User receives presence notifications updates in the workspace", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
await workspacePage.sendPresenceMessage(presenceFixture);
|
||||
|
||||
await expect(page.getByTestId("active-users-list").getByAltText("Princesa Leia")).toHaveCount(2);
|
||||
});
|
||||
|
||||
test.skip("User draws a rect", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-create-rect.json");
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
await workspacePage.rectShapeButton.click();
|
||||
await workspacePage.clickWithDragViewportAt(128, 128, 200, 100);
|
||||
|
||||
const shape = await workspacePage.rootShape.locator("rect");
|
||||
expect(shape).toHaveAttribute("width", "200");
|
||||
expect(shape).toHaveAttribute("height", "100");
|
||||
});
|
|
@ -3,7 +3,7 @@ import { fileURLToPath } from "url";
|
|||
import path from "path";
|
||||
|
||||
const app = express();
|
||||
const port = 3500;
|
||||
const port = 3000;
|
||||
|
||||
const staticPath = path.join(fileURLToPath(import.meta.url), "../../resources/public");
|
||||
app.use(express.static(staticPath));
|
||||
|
|
|
@ -157,8 +157,10 @@
|
|||
(defn resolve-file-media
|
||||
([media]
|
||||
(resolve-file-media media false))
|
||||
([{:keys [id] :as media} thumbnail?]
|
||||
(dm/str
|
||||
(cond-> (u/join public-uri "assets/by-file-media-id/")
|
||||
(true? thumbnail?) (u/join (dm/str id "/thumbnail"))
|
||||
(false? thumbnail?) (u/join (dm/str id))))))
|
||||
([{:keys [id data-uri] :as media} thumbnail?]
|
||||
(if data-uri
|
||||
data-uri
|
||||
(dm/str
|
||||
(cond-> (u/join public-uri "assets/by-file-media-id/")
|
||||
(true? thumbnail?) (u/join (dm/str id "/thumbnail"))
|
||||
(false? thumbnail?) (u/join (dm/str id)))))))
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.grid-layout :as gslg]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.text :as txt]
|
||||
|
@ -1868,7 +1869,8 @@
|
|||
drop-cell (when (ctl/grid-layout? all-objects parent-id)
|
||||
(gslg/get-drop-cell frame-id all-objects position))
|
||||
|
||||
changes (-> (dws/prepare-duplicate-changes all-objects page selected delta it libraries ldata file-id)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(cll/generate-duplicate-changes all-objects page selected delta libraries ldata file-id)
|
||||
(pcb/amend-changes (partial process-rchange media-idx))
|
||||
(pcb/amend-changes (partial change-add-obj-index objects selected index)))
|
||||
|
||||
|
|
|
@ -17,12 +17,6 @@
|
|||
[app.common.logic.libraries :as cll]
|
||||
[app.common.record :as cr]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.modal :as md]
|
||||
|
@ -368,308 +362,6 @@
|
|||
(rx/of (select-shape (:id selected))))))))
|
||||
|
||||
;; --- Duplicate Shapes
|
||||
(declare prepare-duplicate-shape-change)
|
||||
(declare prepare-duplicate-flows)
|
||||
(declare prepare-duplicate-guides)
|
||||
|
||||
(defn prepare-duplicate-changes
|
||||
"Prepare objects to duplicate: generate new id, give them unique names,
|
||||
move to the desired position, and recalculate parents and frames as needed."
|
||||
([all-objects page ids delta it libraries library-data file-id]
|
||||
(let [init-changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects all-objects))]
|
||||
(prepare-duplicate-changes all-objects page ids delta it libraries library-data file-id init-changes)))
|
||||
|
||||
([all-objects page ids delta it libraries library-data file-id init-changes]
|
||||
(let [shapes (map (d/getf all-objects) ids)
|
||||
unames (volatile! (cfh/get-used-names (:objects page)))
|
||||
update-unames! (fn [new-name] (vswap! unames conj new-name))
|
||||
all-ids (reduce #(into %1 (cons %2 (cfh/get-children-ids all-objects %2))) (d/ordered-set) ids)
|
||||
|
||||
;; We need ids-map for remapping the grid layout. But when duplicating the guides
|
||||
;; we calculate a new one because the components will have created new shapes.
|
||||
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
|
||||
|
||||
changes
|
||||
(->> shapes
|
||||
(reduce #(prepare-duplicate-shape-change %1
|
||||
all-objects
|
||||
page
|
||||
unames
|
||||
update-unames!
|
||||
ids-map
|
||||
%2
|
||||
delta
|
||||
nil
|
||||
libraries
|
||||
library-data
|
||||
it
|
||||
file-id)
|
||||
init-changes))
|
||||
|
||||
;; We need to check the changes to get the ids-map
|
||||
ids-map
|
||||
(into {}
|
||||
(comp
|
||||
(filter #(= :add-obj (:type %)))
|
||||
(map #(vector (:old-id %) (-> % :obj :id))))
|
||||
(:redo-changes changes))]
|
||||
|
||||
(-> changes
|
||||
(prepare-duplicate-flows shapes page ids-map)
|
||||
(prepare-duplicate-guides shapes page ids-map delta)))))
|
||||
|
||||
(defn- prepare-duplicate-component-change
|
||||
[changes objects page component-root parent-id frame-id delta libraries library-data]
|
||||
(let [component-id (:component-id component-root)
|
||||
file-id (:component-file component-root)
|
||||
main-component (ctf/get-component libraries file-id component-id)
|
||||
moved-component (gsh/move component-root delta)
|
||||
pos (gpt/point (:x moved-component) (:y moved-component))
|
||||
origin-frame (get-in page [:objects frame-id])
|
||||
delta (cond-> delta
|
||||
(some? origin-frame)
|
||||
(gpt/subtract (-> origin-frame :selrect gpt/point)))
|
||||
|
||||
instantiate-component
|
||||
#(cll/generate-instantiate-component changes
|
||||
objects
|
||||
file-id
|
||||
(:component-id component-root)
|
||||
pos
|
||||
page
|
||||
libraries
|
||||
(:id component-root)
|
||||
parent-id
|
||||
frame-id
|
||||
{})
|
||||
|
||||
restore-component
|
||||
#(let [restore (cll/prepare-restore-component changes library-data (:component-id component-root) page delta (:id component-root) parent-id frame-id)]
|
||||
[(:shape restore) (:changes restore)])
|
||||
|
||||
[_shape changes]
|
||||
(if (nil? main-component)
|
||||
(restore-component)
|
||||
(instantiate-component))]
|
||||
changes))
|
||||
|
||||
;; TODO: move to common.files.shape-helpers
|
||||
(defn- prepare-duplicate-shape-change
|
||||
([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data it file-id]
|
||||
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data it file-id (:frame-id obj) (:parent-id obj) false false true))
|
||||
|
||||
([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data it file-id frame-id parent-id duplicating-component? child? remove-swap-slot?]
|
||||
(cond
|
||||
(nil? obj)
|
||||
changes
|
||||
|
||||
(ctf/is-main-of-known-component? obj libraries)
|
||||
(prepare-duplicate-component-change changes objects page obj parent-id frame-id delta libraries library-data)
|
||||
|
||||
:else
|
||||
(let [frame? (cfh/frame-shape? obj)
|
||||
group? (cfh/group-shape? obj)
|
||||
bool? (cfh/bool-shape? obj)
|
||||
new-id (ids-map (:id obj))
|
||||
parent-id (or parent-id frame-id)
|
||||
parent (get objects parent-id)
|
||||
name (:name obj)
|
||||
|
||||
is-component-root? (or (:saved-component-root obj)
|
||||
;; Backward compatibility
|
||||
(:saved-component-root? obj)
|
||||
(ctk/instance-root? obj))
|
||||
duplicating-component? (or duplicating-component? (ctk/instance-head? obj))
|
||||
is-component-main? (ctk/main-instance? obj)
|
||||
subinstance-head? (ctk/subinstance-head? obj)
|
||||
instance-root? (ctk/instance-root? obj)
|
||||
|
||||
into-component? (and duplicating-component?
|
||||
(ctn/in-any-component? objects parent))
|
||||
|
||||
level-delta (if (some? level-delta)
|
||||
level-delta
|
||||
(ctn/get-nesting-level-delta objects obj parent))
|
||||
new-shape-ref (ctf/advance-shape-ref nil page libraries obj level-delta {:include-deleted? true})
|
||||
|
||||
regenerate-component
|
||||
(fn [changes shape]
|
||||
(let [components-v2 (dm/get-in library-data [:options :components-v2])
|
||||
[_ changes] (cll/generate-add-component-changes changes shape objects file-id (:id page) components-v2)]
|
||||
changes))
|
||||
|
||||
new-obj
|
||||
(-> obj
|
||||
(assoc :id new-id
|
||||
:name name
|
||||
:parent-id parent-id
|
||||
:frame-id frame-id)
|
||||
|
||||
(cond-> (and (not instance-root?)
|
||||
subinstance-head?
|
||||
remove-swap-slot?)
|
||||
(ctk/remove-swap-slot))
|
||||
|
||||
(dissoc :shapes
|
||||
:use-for-thumbnail)
|
||||
|
||||
(cond-> (not is-component-root?)
|
||||
(dissoc :main-instance))
|
||||
|
||||
(cond-> into-component?
|
||||
(dissoc :component-root))
|
||||
|
||||
(cond-> (and (ctk/instance-head? obj)
|
||||
(not into-component?))
|
||||
(assoc :component-root true))
|
||||
|
||||
(cond-> (or frame? group? bool?)
|
||||
(assoc :shapes []))
|
||||
|
||||
(cond-> (and (some? new-shape-ref)
|
||||
(not= new-shape-ref (:shape-ref obj)))
|
||||
(assoc :shape-ref new-shape-ref))
|
||||
|
||||
(gsh/move delta)
|
||||
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))
|
||||
|
||||
(cond-> (ctl/grid-layout? obj)
|
||||
(ctl/remap-grid-cells ids-map)))
|
||||
|
||||
new-obj (cond-> new-obj
|
||||
(not duplicating-component?)
|
||||
(ctk/detach-shape))
|
||||
|
||||
;; We want the first added object to touch it's parent, but not subsequent children
|
||||
changes (-> (pcb/add-object changes new-obj {:ignore-touched (and duplicating-component? child?)})
|
||||
(pcb/amend-last-change #(assoc % :old-id (:id obj)))
|
||||
(cond-> (ctl/grid-layout? objects (:parent-id obj))
|
||||
(-> (pcb/update-shapes [(:parent-id obj)] ctl/assign-cells {:with-objects? true})
|
||||
(pcb/reorder-grid-children [(:parent-id obj)]))))
|
||||
|
||||
changes (cond-> changes
|
||||
(and is-component-root? is-component-main?)
|
||||
(regenerate-component new-obj))
|
||||
|
||||
;; This is needed for the recursive call to find the new object as parent
|
||||
page' (ctst/add-shape (:id new-obj)
|
||||
new-obj
|
||||
{:objects objects}
|
||||
(:frame-id new-obj)
|
||||
(:parent-id new-obj)
|
||||
nil
|
||||
true)]
|
||||
|
||||
(reduce (fn [changes child]
|
||||
(prepare-duplicate-shape-change changes
|
||||
(:objects page')
|
||||
page
|
||||
unames
|
||||
update-unames!
|
||||
ids-map
|
||||
child
|
||||
delta
|
||||
level-delta
|
||||
libraries
|
||||
library-data
|
||||
it
|
||||
file-id
|
||||
(if frame? new-id frame-id)
|
||||
new-id
|
||||
duplicating-component?
|
||||
true
|
||||
(and remove-swap-slot?
|
||||
;; only remove swap slot of children when the current shape
|
||||
;; is not a subinstance head nor a instance root
|
||||
(not subinstance-head?)
|
||||
(not instance-root?))))
|
||||
changes
|
||||
(map (d/getf objects) (:shapes obj)))))))
|
||||
|
||||
(defn- prepare-duplicate-flows
|
||||
[changes shapes page ids-map]
|
||||
(let [flows (-> page :options :flows)
|
||||
unames (volatile! (into #{} (map :name flows)))
|
||||
frames-with-flow (->> shapes
|
||||
(filter #(= (:type %) :frame))
|
||||
(filter #(some? (ctp/get-frame-flow flows (:id %)))))]
|
||||
(if-not (empty? frames-with-flow)
|
||||
(let [update-flows (fn [flows]
|
||||
(reduce
|
||||
(fn [flows frame]
|
||||
(let [name (cfh/generate-unique-name @unames "Flow 1")
|
||||
_ (vswap! unames conj name)
|
||||
new-flow {:id (uuid/next)
|
||||
:name name
|
||||
:starting-frame (get ids-map (:id frame))}]
|
||||
(ctp/add-flow flows new-flow)))
|
||||
flows
|
||||
frames-with-flow))]
|
||||
(pcb/update-page-option changes :flows update-flows))
|
||||
changes)))
|
||||
|
||||
(defn- prepare-duplicate-guides
|
||||
[changes shapes page ids-map delta]
|
||||
(let [guides (get-in page [:options :guides])
|
||||
frames (->> shapes (filter cfh/frame-shape?))
|
||||
|
||||
new-guides
|
||||
(reduce
|
||||
(fn [g frame]
|
||||
(let [new-id (ids-map (:id frame))
|
||||
new-frame (-> frame (gsh/move delta))
|
||||
|
||||
new-guides
|
||||
(->> guides
|
||||
(vals)
|
||||
(filter #(= (:frame-id %) (:id frame)))
|
||||
(map #(-> %
|
||||
(assoc :id (uuid/next))
|
||||
(assoc :frame-id new-id)
|
||||
(assoc :position (if (= (:axis %) :x)
|
||||
(+ (:position %) (- (:x new-frame) (:x frame)))
|
||||
(+ (:position %) (- (:y new-frame) (:y frame))))))))]
|
||||
(cond-> g
|
||||
(not-empty new-guides)
|
||||
(conj (into {} (map (juxt :id identity) new-guides))))))
|
||||
guides
|
||||
frames)]
|
||||
(-> (pcb/with-page changes page)
|
||||
(pcb/set-page-option :guides new-guides))))
|
||||
|
||||
(defn duplicate-changes-update-indices
|
||||
"Updates the changes to correctly set the indexes of the duplicated objects,
|
||||
depending on the index of the original object respect their parent."
|
||||
[objects ids changes]
|
||||
(let [;; index-map is a map that goes from parent-id => vector([id index-in-parent])
|
||||
index-map (reduce (fn [index-map id]
|
||||
(let [parent-id (get-in objects [id :parent-id])
|
||||
parent-index (cfh/get-position-on-parent objects id)]
|
||||
(update index-map parent-id (fnil conj []) [id parent-index])))
|
||||
{}
|
||||
ids)
|
||||
|
||||
inc-indices
|
||||
(fn [[offset result] [id index]]
|
||||
[(inc offset) (conj result [id (+ index offset)])])
|
||||
|
||||
fix-indices
|
||||
(fn [_ entry]
|
||||
(->> entry
|
||||
(sort-by second)
|
||||
(reduce inc-indices [1 []])
|
||||
(second)
|
||||
(into {})))
|
||||
|
||||
objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))]
|
||||
|
||||
(pcb/amend-changes
|
||||
changes
|
||||
(fn [change]
|
||||
(assoc change :index (get objects-indices (:old-id change)))))))
|
||||
|
||||
(defn clear-memorize-duplicated
|
||||
[]
|
||||
|
@ -746,8 +438,9 @@
|
|||
libraries (wsh/get-libraries state)
|
||||
library-data (wsh/get-file state file-id)
|
||||
|
||||
changes (->> (prepare-duplicate-changes objects page ids delta it libraries library-data file-id)
|
||||
(duplicate-changes-update-indices objects ids))
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(cll/generate-duplicate-changes objects page ids delta libraries library-data file-id)
|
||||
(cll/generate-duplicate-changes-update-indices objects ids))
|
||||
|
||||
tags (or (:tags changes) #{})
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.flex-layout :as flex]
|
||||
[app.common.geom.shapes.grid-layout :as grid]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
|
@ -339,8 +340,9 @@
|
|||
selected (set shapes-by-track)
|
||||
|
||||
changes
|
||||
(->> (dwse/prepare-duplicate-changes objects page selected (gpt/point 0 0) it libraries library-data file-id)
|
||||
(dwse/duplicate-changes-update-indices objects selected))
|
||||
(-> (pcb/empty-changes it)
|
||||
(cll/generate-duplicate-changes objects page selected (gpt/point 0 0) libraries library-data file-id)
|
||||
(cll/generate-duplicate-changes-update-indices objects selected))
|
||||
|
||||
;; Creates a map with shape-id => duplicated-shape-id
|
||||
ids-map
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
(mf/defc link
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [action class data-test keyboard-action children]}]
|
||||
[{:keys [action class data-test keyboard-action children data-testid]}]
|
||||
(let [keyboard-action (d/nilv keyboard-action action)]
|
||||
[:a {:on-click action
|
||||
:class class
|
||||
|
@ -20,5 +20,6 @@
|
|||
(when ^boolean (kbd/enter? event)
|
||||
(keyboard-action event)))
|
||||
:tab-index "0"
|
||||
:data-testid data-testid
|
||||
:data-test data-test}
|
||||
children]))
|
||||
|
|
|
@ -783,6 +783,7 @@
|
|||
[:li {:class (stl/css-case :current drafts?
|
||||
:sidebar-nav-item true)}
|
||||
[:& link {:action go-drafts
|
||||
:data-testid "drafts-link-sidebar"
|
||||
:class (stl/css :sidebar-link)
|
||||
:keyboard-action go-drafts-with-key}
|
||||
[:span {:class (stl/css :element-title)} (tr "labels.drafts")]]]
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
:class (stl/css :active-users-opened)
|
||||
:on-click on-close
|
||||
:on-blur on-close}
|
||||
[:ul {:class (stl/css :active-users-list)}
|
||||
[:ul {:class (stl/css :active-users-list) :data-testid "active-users-list"}
|
||||
(for [session sessions]
|
||||
[:& session-widget
|
||||
{:color (:color session)
|
||||
|
@ -66,7 +66,7 @@
|
|||
|
||||
[:button {:class (stl/css-case :active-users true)
|
||||
:on-click on-open}
|
||||
[:ul {:class (stl/css :active-users-list)}
|
||||
[:ul {:class (stl/css :active-users-list) :data-testid "active-users-list"}
|
||||
(when (> num-sessions 2)
|
||||
[:span {:class (stl/css :users-num)} (dm/str "+" (- num-sessions 2))])
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@
|
|||
:auto-focus true
|
||||
:default-value (:name page "")}]]
|
||||
[:*
|
||||
[:span {:class (stl/css :page-name)}
|
||||
[:span {:class (stl/css :page-name) :data-testid "page-name"}
|
||||
(:name page)]
|
||||
[:div {:class (stl/css :page-actions)}
|
||||
(when (and deletable? (not workspace-read-only?))
|
||||
|
|
|
@ -276,7 +276,7 @@
|
|||
(hooks/setup-shortcuts node-editing? drawing-path? text-editing? grid-editing?)
|
||||
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
|
||||
|
||||
[:div.viewport {:style #js {"--zoom" zoom}}
|
||||
[:div.viewport {:style #js {"--zoom" zoom} :data-testid "viewport"}
|
||||
[:& top-bar/top-bar {:layout layout}]
|
||||
[:div.viewport-overlays
|
||||
;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap
|
||||
|
|
49
frontend/test/frontend_tests/basic_shapes_test.cljs
Normal file
49
frontend/test/frontend_tests/basic_shapes_test.cljs
Normal file
|
@ -0,0 +1,49 @@
|
|||
;; 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 INC
|
||||
(ns frontend-tests.basic-shapes-test
|
||||
(:require
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
|
||||
(t/deftest test-update-shape
|
||||
(t/async
|
||||
done
|
||||
(let [;; ==== Setup
|
||||
store
|
||||
(ths/setup-store
|
||||
(-> (cthf/sample-file :file1 :page-label :page1)
|
||||
(cths/add-sample-shape :shape1)))
|
||||
|
||||
;; ==== Action
|
||||
events
|
||||
[(dch/update-shapes [(cthi/id :shape1)]
|
||||
#(assoc % :fills
|
||||
(cths/sample-fills-color :fill-color
|
||||
"#fabada")))]]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [;; ==== Get
|
||||
shape1' (get-in new-state [:workspace-data
|
||||
:pages-index
|
||||
(cthi/id :page1)
|
||||
:objects
|
||||
(cthi/id :shape1)])
|
||||
fills' (:fills shape1')
|
||||
fill' (first fills')]
|
||||
|
||||
(cthf/dump-shape shape1')
|
||||
|
||||
;; ==== Check
|
||||
(t/is (some? shape1'))
|
||||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))))))))
|
|
@ -97,7 +97,7 @@
|
|||
(if (empty? shapes)
|
||||
state
|
||||
(let [[group changes]
|
||||
(dwg/prepare-create-group nil (:objects page) (:id page) shapes prefix true)]
|
||||
(dwg/prepare-create-group (pcb/empty-changes) nil (:objects page) (:id page) shapes prefix true)]
|
||||
|
||||
(swap! idmap assoc label (:id group))
|
||||
(update state :workspace-data
|
||||
|
|
56
frontend/test/frontend_tests/helpers/state.cljs
Normal file
56
frontend/test/frontend_tests/helpers/state.cljs
Normal file
|
@ -0,0 +1,56 @@
|
|||
;; 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 INC
|
||||
|
||||
(ns frontend-tests.helpers.state
|
||||
(:require
|
||||
[app.common.pprint :refer [pprint]]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.main.data.workspace.layout :as layout]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(def ^private initial-state
|
||||
{:workspace-layout layout/default-layout
|
||||
:workspace-global layout/default-global
|
||||
:current-file-id nil
|
||||
:current-page-id nil
|
||||
:workspace-data nil
|
||||
:workspace-libraries {}
|
||||
:features/team #{"components/v2"}})
|
||||
|
||||
(defn- on-error
|
||||
[cause]
|
||||
(js/console.log "STORE ERROR" (.-stack cause))
|
||||
(when-let [data (some-> cause ex-data ::sm/explain)]
|
||||
(pprint (sm/humanize-explain data))))
|
||||
|
||||
(defn setup-store
|
||||
[file]
|
||||
(let [state (-> initial-state
|
||||
(assoc :current-file-id (:id file)
|
||||
:current-page-id (cthf/current-page-id file)
|
||||
:workspace-file (dissoc file :data)
|
||||
:workspace-data (:data file)))
|
||||
store (ptk/store {:state state :on-error on-error})]
|
||||
store))
|
||||
|
||||
(defn run-store
|
||||
[store done events completed-cb]
|
||||
(let [stream (ptk/input-stream store)]
|
||||
(->> stream
|
||||
(rx/take-until (rx/filter #(= :the/end %) stream))
|
||||
(rx/last)
|
||||
(rx/tap (fn []
|
||||
(completed-cb @store)))
|
||||
(rx/subs! (fn [_] (done))
|
||||
(fn [cause]
|
||||
(js/console.log "[error]:" cause))
|
||||
(fn [_]
|
||||
(js/console.log "[complete]"))))
|
||||
(doall (for [event events]
|
||||
(ptk/emit! store event)))
|
||||
(ptk/emit! store :the/end)))
|
File diff suppressed because it is too large
Load diff
|
@ -1,860 +0,0 @@
|
|||
(ns frontend-tests.state-components-test
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.events :as the]
|
||||
[frontend-tests.helpers.libraries :as thl]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
[linked.core :as lks]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before thp/reset-idmap!})
|
||||
|
||||
(t/deftest test-add-component-from-single-shape
|
||||
(t/testing "test-add-component-from-single-shape"
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"}))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Uncomment to debug
|
||||
;; (ctf/dump-tree (get new-state :workspace-data)
|
||||
;; (get new-state :current-page-id)
|
||||
;; (get new-state :workspace-libraries)
|
||||
;; false true)
|
||||
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1 #--> Rect 1
|
||||
;; Rect 1 ---> Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;;
|
||||
(let [shape1 (thp/get-shape new-state :shape1)
|
||||
|
||||
[[group shape1] [c-group c-shape1] component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:parent-id shape1))
|
||||
|
||||
file (wsh/get-local-file new-state)]
|
||||
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name group) "Rect 1"))
|
||||
(t/is (= (:name component) "Rect 1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-group) "Rect 1"))
|
||||
|
||||
(thl/is-from-file group file))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shape (thp/id :shape1))
|
||||
(dwl/add-component)
|
||||
:the/end)))))
|
||||
|
||||
(t/deftest test-add-component-from-several-shapes
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/sample-shape :shape2 :rect
|
||||
{:name "Rect-2"}))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Component 1
|
||||
;; Rect 1
|
||||
;; Rect-2
|
||||
;;
|
||||
;; [Component 1]
|
||||
;; page1 / Component 1
|
||||
;;
|
||||
(let [shape1 (thp/get-shape new-state :shape1)
|
||||
|
||||
[[group shape1 shape2]
|
||||
[c-group c-shape1 c-shape2]
|
||||
component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:parent-id shape1))
|
||||
|
||||
file (wsh/get-local-file new-state)]
|
||||
|
||||
(t/is (= (:name group) "Component 1"))
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect-2"))
|
||||
(t/is (= (:name component) "Component 1"))
|
||||
(t/is (= (:name c-group) "Component 1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect-2"))
|
||||
|
||||
(thl/is-from-file group file))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shapes (lks/set (thp/id :shape1)
|
||||
(thp/id :shape2)))
|
||||
(dwl/add-component)
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-add-component-from-frame
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/sample-shape :shape2 :rect
|
||||
{:name "Rect-2"})
|
||||
(thp/frame-shapes :frame1
|
||||
[(thp/id :shape1)
|
||||
(thp/id :shape2)]))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Group
|
||||
;; Rect 1
|
||||
;; Rect-2
|
||||
;;
|
||||
;; [Group]
|
||||
;; page1 / Group
|
||||
;;
|
||||
(let [[[group shape1 shape2]
|
||||
[c-group c-shape1 c-shape2]
|
||||
component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(thp/id :frame1))
|
||||
|
||||
file (wsh/get-local-file new-state)]
|
||||
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect-2"))
|
||||
(t/is (= (:name group) "Board"))
|
||||
(t/is (= (:name component) "Board"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect-2"))
|
||||
(t/is (= (:name c-group) "Board"))
|
||||
|
||||
(thl/is-from-file group file))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shape (thp/id :frame1))
|
||||
(dwl/add-component)
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-add-component-from-component-instance
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)])
|
||||
(thp/instantiate-component :instance1 (thp/id :component1)))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page: Page]
|
||||
;; Root Frame
|
||||
;; Rect 1 #
|
||||
;; Rect 1
|
||||
;; Rect 1 #
|
||||
;; Rect 1* @--> Rect 1
|
||||
;; Rect 1 ---> Rect 1
|
||||
;;
|
||||
(let [[[instance1 shape1]
|
||||
[c-instance1 c-shape1]
|
||||
component1]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(thp/id :instance1)
|
||||
true)
|
||||
|
||||
[[instance2 instance1' shape1']
|
||||
[c-instance2 c-instance1' c-shape1']
|
||||
component2]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:parent-id instance1))]
|
||||
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name instance1) "Rect 1"))
|
||||
(t/is (= (:name component1) "Rect 1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-instance1) "Rect 1"))
|
||||
|
||||
(t/is (= (:name shape1') "Rect 1"))
|
||||
(t/is (= (:name instance1') "Rect 1"))
|
||||
(t/is (= (:name instance2) "Rect 1"))
|
||||
(t/is (= (:name component2) "Rect 1"))
|
||||
(t/is (= (:name c-shape1') "Rect 1"))
|
||||
(t/is (= (:name c-instance1') "Rect 1"))
|
||||
(t/is (= (:name c-instance2) "Rect 1")))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shape (thp/id :instance1))
|
||||
(dwl/add-component)
|
||||
:the/end))))
|
||||
|
||||
|
||||
(t/deftest test-add-component-from-component-main
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)]))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
(let [file (wsh/get-local-file new-state)
|
||||
components (ctkl/components file)
|
||||
page (thp/current-page new-state)
|
||||
shape1 (thp/get-shape new-state :shape1)
|
||||
parent1 (ctn/get-shape page (:parent-id shape1))
|
||||
main1 (thp/get-shape state :main1)
|
||||
[[instance1 shape1]
|
||||
[c-instance1 c-shape1]
|
||||
component1]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:id main1))]
|
||||
;; Creating a component from a main doesn't generate a new component
|
||||
(t/is (= (count components) 1))
|
||||
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name instance1) "Rect 1"))
|
||||
(t/is (= (:name component1) "Rect 1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-instance1) "Rect 1")))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shape (thp/id :main1))
|
||||
(dwl/add-component)
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-rename-component
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)]))
|
||||
|
||||
main1 (thp/get-shape state :main1)
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;;
|
||||
;; [Renamed component]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
(let [libs (wsh/get-libraries new-state)
|
||||
component (ctf/get-component libs
|
||||
(:component-file main1)
|
||||
(:component-id main1))]
|
||||
(t/is (= (:name component)
|
||||
"Renamed component")))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dwl/rename-component (:component-id main1) "Renamed component")
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-duplicate-component
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)]))
|
||||
|
||||
main1 (thp/get-shape state :main1)
|
||||
component-id (:component-id main1)
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;; Rect 1 #--> Rect 1
|
||||
;; Rect 1 ---> Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
(let [new-component-id (->> (get-in new-state
|
||||
[:workspace-data
|
||||
:components])
|
||||
(keys)
|
||||
(filter #(not= % component-id))
|
||||
(first))
|
||||
|
||||
[[_instance1 _shape1]
|
||||
[_c-instance1 _c-shape1]
|
||||
_component1]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:id main1))
|
||||
|
||||
[[_c-component2 _c-shape2]
|
||||
component2]
|
||||
(thl/resolve-component
|
||||
new-state
|
||||
(:current-file-id new-state)
|
||||
new-component-id)]
|
||||
|
||||
(t/is (= (:name component2) "Rect 1")))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dwl/duplicate-component thp/current-file-id component-id)
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-delete-component
|
||||
(t/async done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect {:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1 [(thp/id :shape1)])
|
||||
(thp/instantiate-component :instance1 (thp/id :component1)))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1 #--> ?
|
||||
;; Rect 1 ---> ?
|
||||
;;;
|
||||
(let [[main1 shape1]
|
||||
(thl/resolve-noninstance
|
||||
new-state
|
||||
(thp/id :main1))
|
||||
|
||||
[[instance1 shape2] [c-instance1 c-shape2] component1]
|
||||
(thl/resolve-instance-and-main-allow-dangling
|
||||
new-state
|
||||
(thp/id :instance1))
|
||||
|
||||
file (wsh/get-local-file new-state)
|
||||
component2 (ctkl/get-component file (thp/id :component1))
|
||||
component3 (ctkl/get-deleted-component file (thp/id :component1))
|
||||
|
||||
saved-objects (:objects component3)
|
||||
saved-main1 (get saved-objects (:shape-ref instance1))
|
||||
saved-shape2 (get saved-objects (:shape-ref shape2))]
|
||||
|
||||
(t/is (nil? main1))
|
||||
(t/is (nil? shape1))
|
||||
|
||||
(t/is (= (:name instance1) "Rect 1"))
|
||||
(t/is (= (:touched instance1) nil))
|
||||
(t/is (not= (:shape-ref instance1) nil))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:touched shape2) nil))
|
||||
(t/is (not= (:shape-ref shape2) nil))
|
||||
(t/is (nil? c-instance1))
|
||||
(t/is (nil? c-shape2))
|
||||
(t/is (nil? component1))
|
||||
|
||||
(t/is (nil? component2))
|
||||
|
||||
(t/is (= (:name component3) "Rect 1"))
|
||||
(t/is (= (:deleted component3) true))
|
||||
(t/is (some? (:objects component3)))
|
||||
|
||||
(t/is (= (:name saved-main1) "Rect 1"))
|
||||
(t/is (= (:name saved-shape2) "Rect 1")))))]
|
||||
(ptk/emit! store
|
||||
(dwl/delete-component {:id (thp/id :component1)})
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-restore-component
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)])
|
||||
(thp/instantiate-component :instance1
|
||||
(thp/id :component1)))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1 #--> Rect 1
|
||||
;; Rect 1 ---> Rect 1
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
(let [[[instance1 shape2] [c-instance1 c-shape2] component1]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(thp/id :instance1))
|
||||
|
||||
file (wsh/get-local-file new-state)
|
||||
component2 (ctkl/get-component file (thp/id :component1))
|
||||
|
||||
saved-objects (:objects component2)]
|
||||
|
||||
(t/is (= (:name instance1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:name c-instance1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 1"))
|
||||
|
||||
(t/is (some? component1))
|
||||
(t/is (some? component2))
|
||||
(t/is (nil? saved-objects)))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dwl/delete-component {:id (thp/id :component1)})
|
||||
(dwl/restore-component thp/current-file-id (thp/id :component1))
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-instantiate-component
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)]))
|
||||
|
||||
file (wsh/get-local-file state)
|
||||
component-id (thp/id :component1)
|
||||
main1 (thp/get-shape state :main1)
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;; Rect 1 #--> Rect 1
|
||||
;; Rect 1 ---> Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
(let [new-instance-id (-> new-state
|
||||
wsh/lookup-selected
|
||||
first)
|
||||
|
||||
[[instance1 shape2]
|
||||
[c-instance1 c-shape2]
|
||||
component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
new-instance-id)]
|
||||
|
||||
(t/is (not= (:id main1) (:id instance1)))
|
||||
(t/is (= (:id component) component-id))
|
||||
(t/is (= (:name instance1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:name c-instance1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 1"))
|
||||
(t/is (= (:component-file instance1)
|
||||
thp/current-file-id)))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dwl/instantiate-component (:id file)
|
||||
component-id
|
||||
(gpt/point 100 100))
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-instantiate-component-from-lib
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)])
|
||||
(thp/move-to-library :lib1 "Library 1")
|
||||
(thp/sample-page))
|
||||
|
||||
library-id (thp/id :lib1)
|
||||
component-id (thp/id :component1)
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1 #--> <Library 1> Rect 1
|
||||
;; Rect 1 ---> <Library 1> Rect 1
|
||||
;;
|
||||
(let [new-instance-id (-> new-state
|
||||
wsh/lookup-selected
|
||||
first)
|
||||
|
||||
[[instance1 shape2]
|
||||
[c-instance1 c-shape2]
|
||||
component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
new-instance-id)]
|
||||
|
||||
(t/is (= (:id component) component-id))
|
||||
(t/is (= (:name instance1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:name c-instance1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 1"))
|
||||
(t/is (= (:component-file instance1) library-id)))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dwl/instantiate-component library-id
|
||||
component-id
|
||||
(gpt/point 100 100))
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-detach-component
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)])
|
||||
(thp/instantiate-component :instance1
|
||||
(thp/id :component1)))
|
||||
|
||||
instance1 (thp/get-shape state :instance1)
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
(let [[instance2 shape1]
|
||||
(thl/resolve-noninstance
|
||||
new-state
|
||||
(:id instance1))]
|
||||
|
||||
(t/is (some? instance2))
|
||||
(t/is (some? shape1)))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dwl/detach-component (:id instance1))
|
||||
:the/end))))
|
||||
|
||||
|
||||
|
||||
(t/deftest test-add-nested-component-instance
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)])
|
||||
(thp/instantiate-component :instance1 (thp/id :component1)))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;; Board
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
;; [Board]
|
||||
;; page1 / Board
|
||||
;;
|
||||
(let [instance1 (thp/get-shape new-state :instance1)
|
||||
|
||||
[[group shape1 shape2]
|
||||
[c-group c-shape1 c-shape2]
|
||||
component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:parent-id instance1))]
|
||||
|
||||
(t/is (= (:name group) "Board"))
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:name component) "Board"))
|
||||
(t/is (= (:name c-group) "Board"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 1")))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shape (thp/id :instance1))
|
||||
(dwsh/create-artboard-from-selection)
|
||||
(dwl/add-component)
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-add-nested-component-main
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"}))
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Board
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
;;
|
||||
(let [file (wsh/get-local-file new-state)
|
||||
components (ctkl/components file)
|
||||
page (thp/current-page new-state)
|
||||
|
||||
shape1 (thp/get-shape new-state :shape1)
|
||||
parent1 (ctn/get-shape page (:parent-id shape1))
|
||||
|
||||
[[group shape1]
|
||||
[c-group c-shape1]
|
||||
component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:parent-id shape1))]
|
||||
|
||||
;; Creating a component from something containing a main doesn't generate a new component
|
||||
(t/is (= (count components) 1))
|
||||
|
||||
(t/is (= (:name group) "Rect 1"))
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name component) "Rect 1"))
|
||||
(t/is (= (:name c-group) "Rect 1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1")))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shape (thp/id :shape1))
|
||||
(dwl/add-component)
|
||||
(dwsh/create-artboard-from-selection)
|
||||
(dwl/add-component)
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-instantiate-nested-component
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)])
|
||||
(thp/make-component :main2 :component-2
|
||||
[(thp/id :main1)]))
|
||||
|
||||
file (wsh/get-local-file state)
|
||||
main1 (thp/get-shape state :main1)
|
||||
main2 (thp/get-shape state :main2)
|
||||
component-id (:component-id main2)
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;; Rect 1
|
||||
;; Rect 1 #--> Rect 1
|
||||
;; Rect 1 @--> Rect 1
|
||||
;; Rect 1 ---> Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
;; [Rect 1]
|
||||
;; page1 / Rect 1
|
||||
;;
|
||||
(let [new-instance-id (-> new-state
|
||||
wsh/lookup-selected
|
||||
first)
|
||||
|
||||
[[instance1 shape1 shape2]
|
||||
[c-instance1 c-shape1 c-shape2]
|
||||
component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
new-instance-id)]
|
||||
|
||||
;; TODO: get and check the instance inside component [Rect-2]
|
||||
|
||||
(t/is (not= (:id main1) (:id instance1)))
|
||||
(t/is (= (:id component) component-id))
|
||||
(t/is (= (:name instance1) "Rect 1"))
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:name c-instance1) "Rect 1"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 1")))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dwl/instantiate-component (:id file)
|
||||
(:component-id main2)
|
||||
(gpt/point 100 100))
|
||||
:the/end))))
|
||||
|
||||
(t/deftest test-instantiate-nested-component-from-lib
|
||||
(t/async
|
||||
done
|
||||
(let [state (-> thp/initial-state
|
||||
(thp/sample-page)
|
||||
(thp/sample-shape :shape1 :rect
|
||||
{:name "Rect 1"})
|
||||
(thp/make-component :main1 :component1
|
||||
[(thp/id :shape1)])
|
||||
(thp/move-to-library :lib1 "Library 1")
|
||||
(thp/sample-page)
|
||||
(thp/instantiate-component :instance1
|
||||
(thp/id :component1)
|
||||
(thp/id :lib1)))
|
||||
|
||||
file (wsh/get-local-file state)
|
||||
library-id (thp/id :lib1)
|
||||
|
||||
store (the/prepare-store state done
|
||||
(fn [new-state]
|
||||
;; Expected shape tree:
|
||||
;;
|
||||
;; [Page]
|
||||
;; Root Frame
|
||||
;; Group
|
||||
;; Rect 1 #--> <Library 1> Rect 1
|
||||
;; Rect 1 ---> <Library 1> Rect 1
|
||||
;;
|
||||
;; [Group]
|
||||
;; page1 / Group
|
||||
;;
|
||||
(let [instance1 (thp/get-shape new-state :instance1)
|
||||
|
||||
[[group1 shape1 shape2] [c-group1 c-shape1 c-shape2] _component]
|
||||
(thl/resolve-instance-and-main
|
||||
new-state
|
||||
(:parent-id instance1))]
|
||||
|
||||
(t/is (= (:name group1) "Board"))
|
||||
(t/is (= (:name shape1) "Rect 1"))
|
||||
(t/is (= (:name shape2) "Rect 1"))
|
||||
(t/is (= (:name c-group1) "Board"))
|
||||
(t/is (= (:name c-shape1) "Rect 1"))
|
||||
(t/is (= (:name c-shape2) "Rect 1"))
|
||||
(t/is (= (:component-file group1) thp/current-file-id))
|
||||
(t/is (= (:component-file shape1) library-id))
|
||||
(t/is (= (:component-file shape2) nil))
|
||||
(t/is (= (:component-file c-group1) (:id file)))
|
||||
(t/is (= (:component-file c-shape1) library-id))
|
||||
(t/is (= (:component-file c-shape2) nil)))))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
(dw/select-shape (thp/id :instance1))
|
||||
(dwsh/create-artboard-from-selection)
|
||||
(dwl/add-component)
|
||||
:the/end))))
|
91
package-lock.json
generated
Normal file
91
package-lock.json
generated
Normal file
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"name": "penpot",
|
||||
"version": "1.20.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "penpot",
|
||||
"version": "1.20.0",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.43.1",
|
||||
"@types/node": "^20.12.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
|
||||
"integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.43.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
|
||||
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz",
|
||||
"integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.43.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz",
|
||||
"integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,5 +18,9 @@
|
|||
"lint:clj:backend": "clj-kondo --parallel=true --lint backend/src",
|
||||
"lint:clj:exporter": "clj-kondo --parallel=true --lint exporter/src",
|
||||
"lint:clj": "yarn run lint:clj:common && yarn run lint:clj:frontend && yarn run lint:clj:backend && yarn run lint:clj:exporter"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.43.1",
|
||||
"@types/node": "^20.12.7"
|
||||
}
|
||||
}
|
||||
|
|
77
playwright.config.ts
Normal file
77
playwright.config.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
907
yarn.lock
907
yarn.lock
|
@ -5,8 +5,915 @@ __metadata:
|
|||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"@isaacs/cliui@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||
dependencies:
|
||||
string-width: "npm:^5.1.2"
|
||||
string-width-cjs: "npm:string-width@^4.2.0"
|
||||
strip-ansi: "npm:^7.0.1"
|
||||
strip-ansi-cjs: "npm:strip-ansi@^6.0.1"
|
||||
wrap-ansi: "npm:^8.1.0"
|
||||
wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0"
|
||||
checksum: b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@npmcli/agent@npm:^2.0.0":
|
||||
version: 2.2.2
|
||||
resolution: "@npmcli/agent@npm:2.2.2"
|
||||
dependencies:
|
||||
agent-base: "npm:^7.1.0"
|
||||
http-proxy-agent: "npm:^7.0.0"
|
||||
https-proxy-agent: "npm:^7.0.1"
|
||||
lru-cache: "npm:^10.0.1"
|
||||
socks-proxy-agent: "npm:^8.0.3"
|
||||
checksum: 325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@npmcli/fs@npm:^3.1.0":
|
||||
version: 3.1.1
|
||||
resolution: "@npmcli/fs@npm:3.1.1"
|
||||
dependencies:
|
||||
semver: "npm:^7.3.5"
|
||||
checksum: c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pkgjs/parseargs@npm:^0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "@pkgjs/parseargs@npm:0.11.0"
|
||||
checksum: 5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@playwright/test@npm:^1.43.1":
|
||||
version: 1.43.1
|
||||
resolution: "@playwright/test@npm:1.43.1"
|
||||
dependencies:
|
||||
playwright: "npm:1.43.1"
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 6f1398c3c66657729a14d7c2d239e2f678c37610c3163b4ad1f028cbb6b88fc845cd9033a25d35436fa86d3dfcc57ecb49028c09f7aea1389c4257e4ac9124cd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^20.12.7":
|
||||
version: 20.12.7
|
||||
resolution: "@types/node@npm:20.12.7"
|
||||
dependencies:
|
||||
undici-types: "npm:~5.26.4"
|
||||
checksum: dce80d63a3b91892b321af823d624995c61e39c6a223cc0ac481a44d337640cc46931d33efb3beeed75f5c85c3bda1d97cef4c5cd4ec333caf5dee59cff6eca0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"abbrev@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "abbrev@npm:2.0.0"
|
||||
checksum: f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1":
|
||||
version: 7.1.1
|
||||
resolution: "agent-base@npm:7.1.1"
|
||||
dependencies:
|
||||
debug: "npm:^4.3.4"
|
||||
checksum: e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aggregate-error@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "aggregate-error@npm:3.1.0"
|
||||
dependencies:
|
||||
clean-stack: "npm:^2.0.0"
|
||||
indent-string: "npm:^4.0.0"
|
||||
checksum: a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-regex@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "ansi-regex@npm:5.0.1"
|
||||
checksum: 9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-regex@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "ansi-regex@npm:6.0.1"
|
||||
checksum: cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^4.0.0":
|
||||
version: 4.3.0
|
||||
resolution: "ansi-styles@npm:4.3.0"
|
||||
dependencies:
|
||||
color-convert: "npm:^2.0.1"
|
||||
checksum: 895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^6.1.0":
|
||||
version: 6.2.1
|
||||
resolution: "ansi-styles@npm:6.2.1"
|
||||
checksum: 5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"balanced-match@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "balanced-match@npm:1.0.2"
|
||||
checksum: 9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"brace-expansion@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "brace-expansion@npm:2.0.1"
|
||||
dependencies:
|
||||
balanced-match: "npm:^1.0.0"
|
||||
checksum: b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cacache@npm:^18.0.0":
|
||||
version: 18.0.3
|
||||
resolution: "cacache@npm:18.0.3"
|
||||
dependencies:
|
||||
"@npmcli/fs": "npm:^3.1.0"
|
||||
fs-minipass: "npm:^3.0.0"
|
||||
glob: "npm:^10.2.2"
|
||||
lru-cache: "npm:^10.0.1"
|
||||
minipass: "npm:^7.0.3"
|
||||
minipass-collect: "npm:^2.0.1"
|
||||
minipass-flush: "npm:^1.0.5"
|
||||
minipass-pipeline: "npm:^1.2.4"
|
||||
p-map: "npm:^4.0.0"
|
||||
ssri: "npm:^10.0.0"
|
||||
tar: "npm:^6.1.11"
|
||||
unique-filename: "npm:^3.0.0"
|
||||
checksum: dfda92840bb371fb66b88c087c61a74544363b37a265023223a99965b16a16bbb87661fe4948718d79df6e0cc04e85e62784fbcf1832b2a5e54ff4c46fbb45b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chownr@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "chownr@npm:2.0.0"
|
||||
checksum: 594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"clean-stack@npm:^2.0.0":
|
||||
version: 2.2.0
|
||||
resolution: "clean-stack@npm:2.2.0"
|
||||
checksum: 1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-convert@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "color-convert@npm:2.0.1"
|
||||
dependencies:
|
||||
color-name: "npm:~1.1.4"
|
||||
checksum: 37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-name@npm:~1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "color-name@npm:1.1.4"
|
||||
checksum: a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.0":
|
||||
version: 7.0.3
|
||||
resolution: "cross-spawn@npm:7.0.3"
|
||||
dependencies:
|
||||
path-key: "npm:^3.1.0"
|
||||
shebang-command: "npm:^2.0.0"
|
||||
which: "npm:^2.0.1"
|
||||
checksum: 5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:4, debug@npm:^4.3.4":
|
||||
version: 4.3.4
|
||||
resolution: "debug@npm:4.3.4"
|
||||
dependencies:
|
||||
ms: "npm:2.1.2"
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
checksum: cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eastasianwidth@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "eastasianwidth@npm:0.2.0"
|
||||
checksum: 26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"emoji-regex@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "emoji-regex@npm:8.0.0"
|
||||
checksum: b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"emoji-regex@npm:^9.2.2":
|
||||
version: 9.2.2
|
||||
resolution: "emoji-regex@npm:9.2.2"
|
||||
checksum: af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"encoding@npm:^0.1.13":
|
||||
version: 0.1.13
|
||||
resolution: "encoding@npm:0.1.13"
|
||||
dependencies:
|
||||
iconv-lite: "npm:^0.6.2"
|
||||
checksum: 36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"env-paths@npm:^2.2.0":
|
||||
version: 2.2.1
|
||||
resolution: "env-paths@npm:2.2.1"
|
||||
checksum: 285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"err-code@npm:^2.0.2":
|
||||
version: 2.0.3
|
||||
resolution: "err-code@npm:2.0.3"
|
||||
checksum: b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"exponential-backoff@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "exponential-backoff@npm:3.1.1"
|
||||
checksum: 160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"foreground-child@npm:^3.1.0":
|
||||
version: 3.1.1
|
||||
resolution: "foreground-child@npm:3.1.1"
|
||||
dependencies:
|
||||
cross-spawn: "npm:^7.0.0"
|
||||
signal-exit: "npm:^4.0.1"
|
||||
checksum: 9700a0285628abaeb37007c9a4d92bd49f67210f09067638774338e146c8e9c825c5c877f072b2f75f41dc6a2d0be8664f79ffc03f6576649f54a84fb9b47de0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-minipass@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "fs-minipass@npm:2.1.0"
|
||||
dependencies:
|
||||
minipass: "npm:^3.0.0"
|
||||
checksum: 703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-minipass@npm:^3.0.0":
|
||||
version: 3.0.3
|
||||
resolution: "fs-minipass@npm:3.0.3"
|
||||
dependencies:
|
||||
minipass: "npm:^7.0.3"
|
||||
checksum: 63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fsevents@npm:2.3.2":
|
||||
version: 2.3.2
|
||||
resolution: "fsevents@npm:2.3.2"
|
||||
dependencies:
|
||||
node-gyp: "npm:latest"
|
||||
checksum: be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b
|
||||
conditions: os=darwin
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin<compat/fsevents>":
|
||||
version: 2.3.2
|
||||
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin<compat/fsevents>::version=2.3.2&hash=df0bf1"
|
||||
dependencies:
|
||||
node-gyp: "npm:latest"
|
||||
conditions: os=darwin
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^10.2.2, glob@npm:^10.3.10":
|
||||
version: 10.3.15
|
||||
resolution: "glob@npm:10.3.15"
|
||||
dependencies:
|
||||
foreground-child: "npm:^3.1.0"
|
||||
jackspeak: "npm:^2.3.6"
|
||||
minimatch: "npm:^9.0.1"
|
||||
minipass: "npm:^7.0.4"
|
||||
path-scurry: "npm:^1.11.0"
|
||||
bin:
|
||||
glob: dist/esm/bin.mjs
|
||||
checksum: cda748ddc181b31b3df9548c0991800406d5cc3b3f8110e37a8751ec1e39f37cdae7d7782d5422d7df92775121cdf00599992dff22f7ff1260344843af227c2b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graceful-fs@npm:^4.2.6":
|
||||
version: 4.2.11
|
||||
resolution: "graceful-fs@npm:4.2.11"
|
||||
checksum: 386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-cache-semantics@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "http-cache-semantics@npm:4.1.1"
|
||||
checksum: ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-proxy-agent@npm:^7.0.0":
|
||||
version: 7.0.2
|
||||
resolution: "http-proxy-agent@npm:7.0.2"
|
||||
dependencies:
|
||||
agent-base: "npm:^7.1.0"
|
||||
debug: "npm:^4.3.4"
|
||||
checksum: 4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"https-proxy-agent@npm:^7.0.1":
|
||||
version: 7.0.4
|
||||
resolution: "https-proxy-agent@npm:7.0.4"
|
||||
dependencies:
|
||||
agent-base: "npm:^7.0.2"
|
||||
debug: "npm:4"
|
||||
checksum: bc4f7c38da32a5fc622450b6cb49a24ff596f9bd48dcedb52d2da3fa1c1a80e100fb506bd59b326c012f21c863c69b275c23de1a01d0b84db396822fdf25e52b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:^0.6.2":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
dependencies:
|
||||
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
|
||||
checksum: 98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"imurmurhash@npm:^0.1.4":
|
||||
version: 0.1.4
|
||||
resolution: "imurmurhash@npm:0.1.4"
|
||||
checksum: 8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"indent-string@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "indent-string@npm:4.0.0"
|
||||
checksum: 1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ip-address@npm:^9.0.5":
|
||||
version: 9.0.5
|
||||
resolution: "ip-address@npm:9.0.5"
|
||||
dependencies:
|
||||
jsbn: "npm:1.1.0"
|
||||
sprintf-js: "npm:^1.1.3"
|
||||
checksum: 331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-fullwidth-code-point@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "is-fullwidth-code-point@npm:3.0.0"
|
||||
checksum: bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-lambda@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "is-lambda@npm:1.0.1"
|
||||
checksum: 85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isexe@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "isexe@npm:2.0.0"
|
||||
checksum: 228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isexe@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "isexe@npm:3.1.1"
|
||||
checksum: 9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jackspeak@npm:^2.3.6":
|
||||
version: 2.3.6
|
||||
resolution: "jackspeak@npm:2.3.6"
|
||||
dependencies:
|
||||
"@isaacs/cliui": "npm:^8.0.2"
|
||||
"@pkgjs/parseargs": "npm:^0.11.0"
|
||||
dependenciesMeta:
|
||||
"@pkgjs/parseargs":
|
||||
optional: true
|
||||
checksum: f01d8f972d894cd7638bc338e9ef5ddb86f7b208ce177a36d718eac96ec86638a6efa17d0221b10073e64b45edc2ce15340db9380b1f5d5c5d000cbc517dc111
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsbn@npm:1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "jsbn@npm:1.1.0"
|
||||
checksum: 4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
|
||||
version: 10.2.2
|
||||
resolution: "lru-cache@npm:10.2.2"
|
||||
checksum: 402d31094335851220d0b00985084288136136992979d0e015f0f1697e15d1c86052d7d53ae86b614e5b058425606efffc6969a31a091085d7a2b80a8a1e26d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"make-fetch-happen@npm:^13.0.0":
|
||||
version: 13.0.1
|
||||
resolution: "make-fetch-happen@npm:13.0.1"
|
||||
dependencies:
|
||||
"@npmcli/agent": "npm:^2.0.0"
|
||||
cacache: "npm:^18.0.0"
|
||||
http-cache-semantics: "npm:^4.1.1"
|
||||
is-lambda: "npm:^1.0.1"
|
||||
minipass: "npm:^7.0.2"
|
||||
minipass-fetch: "npm:^3.0.0"
|
||||
minipass-flush: "npm:^1.0.5"
|
||||
minipass-pipeline: "npm:^1.2.4"
|
||||
negotiator: "npm:^0.6.3"
|
||||
proc-log: "npm:^4.2.0"
|
||||
promise-retry: "npm:^2.0.1"
|
||||
ssri: "npm:^10.0.0"
|
||||
checksum: df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^9.0.1":
|
||||
version: 9.0.4
|
||||
resolution: "minimatch@npm:9.0.4"
|
||||
dependencies:
|
||||
brace-expansion: "npm:^2.0.1"
|
||||
checksum: 2c16f21f50e64922864e560ff97c587d15fd491f65d92a677a344e970fe62aafdbeafe648965fa96d33c061b4d0eabfe0213466203dd793367e7f28658cf6414
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass-collect@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "minipass-collect@npm:2.0.1"
|
||||
dependencies:
|
||||
minipass: "npm:^7.0.3"
|
||||
checksum: 5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass-fetch@npm:^3.0.0":
|
||||
version: 3.0.5
|
||||
resolution: "minipass-fetch@npm:3.0.5"
|
||||
dependencies:
|
||||
encoding: "npm:^0.1.13"
|
||||
minipass: "npm:^7.0.3"
|
||||
minipass-sized: "npm:^1.0.3"
|
||||
minizlib: "npm:^2.1.2"
|
||||
dependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
checksum: 9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass-flush@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "minipass-flush@npm:1.0.5"
|
||||
dependencies:
|
||||
minipass: "npm:^3.0.0"
|
||||
checksum: 2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass-pipeline@npm:^1.2.4":
|
||||
version: 1.2.4
|
||||
resolution: "minipass-pipeline@npm:1.2.4"
|
||||
dependencies:
|
||||
minipass: "npm:^3.0.0"
|
||||
checksum: cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass-sized@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "minipass-sized@npm:1.0.3"
|
||||
dependencies:
|
||||
minipass: "npm:^3.0.0"
|
||||
checksum: 298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass@npm:^3.0.0":
|
||||
version: 3.3.6
|
||||
resolution: "minipass@npm:3.3.6"
|
||||
dependencies:
|
||||
yallist: "npm:^4.0.0"
|
||||
checksum: a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "minipass@npm:5.0.0"
|
||||
checksum: a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4":
|
||||
version: 7.1.1
|
||||
resolution: "minipass@npm:7.1.1"
|
||||
checksum: fdccc2f99c31083f45f881fd1e6971d798e333e078ab3c8988fb818c470fbd5e935388ad9adb286397eba50baebf46ef8ff487c8d3f455a69c6f3efc327bdff9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "minizlib@npm:2.1.2"
|
||||
dependencies:
|
||||
minipass: "npm:^3.0.0"
|
||||
yallist: "npm:^4.0.0"
|
||||
checksum: 64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mkdirp@npm:^1.0.3":
|
||||
version: 1.0.4
|
||||
resolution: "mkdirp@npm:1.0.4"
|
||||
bin:
|
||||
mkdirp: bin/cmd.js
|
||||
checksum: 46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ms@npm:2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "ms@npm:2.1.2"
|
||||
checksum: a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"negotiator@npm:^0.6.3":
|
||||
version: 0.6.3
|
||||
resolution: "negotiator@npm:0.6.3"
|
||||
checksum: 3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp@npm:latest":
|
||||
version: 10.1.0
|
||||
resolution: "node-gyp@npm:10.1.0"
|
||||
dependencies:
|
||||
env-paths: "npm:^2.2.0"
|
||||
exponential-backoff: "npm:^3.1.1"
|
||||
glob: "npm:^10.3.10"
|
||||
graceful-fs: "npm:^4.2.6"
|
||||
make-fetch-happen: "npm:^13.0.0"
|
||||
nopt: "npm:^7.0.0"
|
||||
proc-log: "npm:^3.0.0"
|
||||
semver: "npm:^7.3.5"
|
||||
tar: "npm:^6.1.2"
|
||||
which: "npm:^4.0.0"
|
||||
bin:
|
||||
node-gyp: bin/node-gyp.js
|
||||
checksum: 9cc821111ca244a01fb7f054db7523ab0a0cd837f665267eb962eb87695d71fb1e681f9e21464cc2fd7c05530dc4c81b810bca1a88f7d7186909b74477491a3c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nopt@npm:^7.0.0":
|
||||
version: 7.2.1
|
||||
resolution: "nopt@npm:7.2.1"
|
||||
dependencies:
|
||||
abbrev: "npm:^2.0.0"
|
||||
bin:
|
||||
nopt: bin/nopt.js
|
||||
checksum: a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-map@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "p-map@npm:4.0.0"
|
||||
dependencies:
|
||||
aggregate-error: "npm:^3.0.0"
|
||||
checksum: 592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-key@npm:^3.1.0":
|
||||
version: 3.1.1
|
||||
resolution: "path-key@npm:3.1.1"
|
||||
checksum: 748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-scurry@npm:^1.11.0":
|
||||
version: 1.11.1
|
||||
resolution: "path-scurry@npm:1.11.1"
|
||||
dependencies:
|
||||
lru-cache: "npm:^10.2.0"
|
||||
minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
checksum: 32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"penpot@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "penpot@workspace:."
|
||||
dependencies:
|
||||
"@playwright/test": "npm:^1.43.1"
|
||||
"@types/node": "npm:^20.12.7"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"playwright-core@npm:1.43.1":
|
||||
version: 1.43.1
|
||||
resolution: "playwright-core@npm:1.43.1"
|
||||
bin:
|
||||
playwright-core: cli.js
|
||||
checksum: e99f087c5f2b9ab6c379945311ea6e9e90c33cefecd8f950a0716e498dfdded738d6738266af307806d7730eacda8410c7563030690b9acf80c0b268781470b6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:1.43.1":
|
||||
version: 1.43.1
|
||||
resolution: "playwright@npm:1.43.1"
|
||||
dependencies:
|
||||
fsevents: "npm:2.3.2"
|
||||
playwright-core: "npm:1.43.1"
|
||||
dependenciesMeta:
|
||||
fsevents:
|
||||
optional: true
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 7edc1e12b8f3b791c7e8d1f9c595be35c6eaf8100f9550d5e35e979aca0bc229734e65f200f2a02dc7e21630cc40c171d7b25f5f6ccf628c79e4a2d4690909ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proc-log@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "proc-log@npm:3.0.0"
|
||||
checksum: f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proc-log@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "proc-log@npm:4.2.0"
|
||||
checksum: 17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"promise-retry@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "promise-retry@npm:2.0.1"
|
||||
dependencies:
|
||||
err-code: "npm:^2.0.2"
|
||||
retry: "npm:^0.12.0"
|
||||
checksum: 9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"retry@npm:^0.12.0":
|
||||
version: 0.12.0
|
||||
resolution: "retry@npm:0.12.0"
|
||||
checksum: 59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"safer-buffer@npm:>= 2.1.2 < 3.0.0":
|
||||
version: 2.1.2
|
||||
resolution: "safer-buffer@npm:2.1.2"
|
||||
checksum: 7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^7.3.5":
|
||||
version: 7.6.2
|
||||
resolution: "semver@npm:7.6.2"
|
||||
bin:
|
||||
semver: bin/semver.js
|
||||
checksum: 97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shebang-command@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "shebang-command@npm:2.0.0"
|
||||
dependencies:
|
||||
shebang-regex: "npm:^3.0.0"
|
||||
checksum: a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shebang-regex@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "shebang-regex@npm:3.0.0"
|
||||
checksum: 1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"signal-exit@npm:^4.0.1":
|
||||
version: 4.1.0
|
||||
resolution: "signal-exit@npm:4.1.0"
|
||||
checksum: 41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"smart-buffer@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "smart-buffer@npm:4.2.0"
|
||||
checksum: a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"socks-proxy-agent@npm:^8.0.3":
|
||||
version: 8.0.3
|
||||
resolution: "socks-proxy-agent@npm:8.0.3"
|
||||
dependencies:
|
||||
agent-base: "npm:^7.1.1"
|
||||
debug: "npm:^4.3.4"
|
||||
socks: "npm:^2.7.1"
|
||||
checksum: 4950529affd8ccd6951575e21c1b7be8531b24d924aa4df3ee32df506af34b618c4e50d261f4cc603f1bfd8d426915b7d629966c8ce45b05fb5ad8c8b9a6459d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"socks@npm:^2.7.1":
|
||||
version: 2.8.3
|
||||
resolution: "socks@npm:2.8.3"
|
||||
dependencies:
|
||||
ip-address: "npm:^9.0.5"
|
||||
smart-buffer: "npm:^4.2.0"
|
||||
checksum: d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sprintf-js@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "sprintf-js@npm:1.1.3"
|
||||
checksum: 09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ssri@npm:^10.0.0":
|
||||
version: 10.0.6
|
||||
resolution: "ssri@npm:10.0.6"
|
||||
dependencies:
|
||||
minipass: "npm:^7.0.3"
|
||||
checksum: e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0":
|
||||
version: 4.2.3
|
||||
resolution: "string-width@npm:4.2.3"
|
||||
dependencies:
|
||||
emoji-regex: "npm:^8.0.0"
|
||||
is-fullwidth-code-point: "npm:^3.0.0"
|
||||
strip-ansi: "npm:^6.0.1"
|
||||
checksum: 1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^5.0.1, string-width@npm:^5.1.2":
|
||||
version: 5.1.2
|
||||
resolution: "string-width@npm:5.1.2"
|
||||
dependencies:
|
||||
eastasianwidth: "npm:^0.2.0"
|
||||
emoji-regex: "npm:^9.2.2"
|
||||
strip-ansi: "npm:^7.0.1"
|
||||
checksum: ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "strip-ansi@npm:6.0.1"
|
||||
dependencies:
|
||||
ansi-regex: "npm:^5.0.1"
|
||||
checksum: 1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^7.0.1":
|
||||
version: 7.1.0
|
||||
resolution: "strip-ansi@npm:7.1.0"
|
||||
dependencies:
|
||||
ansi-regex: "npm:^6.0.1"
|
||||
checksum: a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:^6.1.11, tar@npm:^6.1.2":
|
||||
version: 6.2.1
|
||||
resolution: "tar@npm:6.2.1"
|
||||
dependencies:
|
||||
chownr: "npm:^2.0.0"
|
||||
fs-minipass: "npm:^2.0.0"
|
||||
minipass: "npm:^5.0.0"
|
||||
minizlib: "npm:^2.1.1"
|
||||
mkdirp: "npm:^1.0.3"
|
||||
yallist: "npm:^4.0.0"
|
||||
checksum: a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~5.26.4":
|
||||
version: 5.26.5
|
||||
resolution: "undici-types@npm:5.26.5"
|
||||
checksum: bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unique-filename@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "unique-filename@npm:3.0.0"
|
||||
dependencies:
|
||||
unique-slug: "npm:^4.0.0"
|
||||
checksum: 6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unique-slug@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "unique-slug@npm:4.0.0"
|
||||
dependencies:
|
||||
imurmurhash: "npm:^0.1.4"
|
||||
checksum: cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which@npm:^2.0.1":
|
||||
version: 2.0.2
|
||||
resolution: "which@npm:2.0.2"
|
||||
dependencies:
|
||||
isexe: "npm:^2.0.0"
|
||||
bin:
|
||||
node-which: ./bin/node-which
|
||||
checksum: 66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "which@npm:4.0.0"
|
||||
dependencies:
|
||||
isexe: "npm:^3.1.1"
|
||||
bin:
|
||||
node-which: bin/which.js
|
||||
checksum: 449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "wrap-ansi@npm:7.0.0"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^4.0.0"
|
||||
string-width: "npm:^4.1.0"
|
||||
strip-ansi: "npm:^6.0.0"
|
||||
checksum: d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi@npm:^8.1.0":
|
||||
version: 8.1.0
|
||||
resolution: "wrap-ansi@npm:8.1.0"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^6.1.0"
|
||||
string-width: "npm:^5.0.1"
|
||||
strip-ansi: "npm:^7.0.1"
|
||||
checksum: 138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yallist@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "yallist@npm:4.0.0"
|
||||
checksum: 2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
|
Loading…
Add table
Reference in a new issue