mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 19:06:18 +00:00
🐛 Fix vector index out of bounds in viewer zoom-to-fit/fill (#8834)
Clamp the frame index to the valid range in zoom-to-fit and zoom-to-fill events before accessing the frames vector. When the URL query parameter :index exceeds the number of frames on the page (e.g. index=1 with a single frame), nth would throw "No item 1 in vector of length 1". Also adds unit tests covering the boundary condition.
This commit is contained in:
parent
81b1b253f1
commit
3ff1acfb6a
@ -304,6 +304,7 @@
|
|||||||
index (some-> (:index params) parse-long)
|
index (some-> (:index params) parse-long)
|
||||||
|
|
||||||
frames (dm/get-in state [:viewer :pages page-id :frames])
|
frames (dm/get-in state [:viewer :pages page-id :frames])
|
||||||
|
index (min (or index 0) (max 0 (dec (count frames))))
|
||||||
srect (-> (nth frames index)
|
srect (-> (nth frames index)
|
||||||
(get :selrect))
|
(get :selrect))
|
||||||
osize (dm/get-in state [:viewer-local :viewport-size])
|
osize (dm/get-in state [:viewer-local :viewport-size])
|
||||||
@ -327,6 +328,7 @@
|
|||||||
index (some-> (:index params) parse-long)
|
index (some-> (:index params) parse-long)
|
||||||
|
|
||||||
frames (dm/get-in state [:viewer :pages page-id :frames])
|
frames (dm/get-in state [:viewer :pages page-id :frames])
|
||||||
|
index (min (or index 0) (max 0 (dec (count frames))))
|
||||||
srect (-> (nth frames index)
|
srect (-> (nth frames index)
|
||||||
(get :selrect))
|
(get :selrect))
|
||||||
|
|
||||||
|
|||||||
69
frontend/test/frontend_tests/data/viewer_test.cljs
Normal file
69
frontend/test/frontend_tests/data/viewer_test.cljs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
;; 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.data.viewer-test
|
||||||
|
(:require
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.main.data.viewer :as dv]
|
||||||
|
[cljs.test :as t]
|
||||||
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
|
(def ^:private page-id
|
||||||
|
(uuid/custom 1 1))
|
||||||
|
|
||||||
|
(defn- base-state
|
||||||
|
"Build a minimal viewer state with the given frames and query-params."
|
||||||
|
[{:keys [frames index]}]
|
||||||
|
{:route {:params {:query {:page-id (str page-id)
|
||||||
|
:index (str index)}}}
|
||||||
|
:viewer {:pages {page-id {:frames frames}}}
|
||||||
|
:viewer-local {:viewport-size {:width 1000 :height 800}}})
|
||||||
|
|
||||||
|
(t/deftest zoom-to-fit-clamps-out-of-bounds-index
|
||||||
|
(t/testing "index exceeds frame count"
|
||||||
|
(let [state (base-state {:frames [{:selrect {:width 100 :height 100}}]
|
||||||
|
:index 1})
|
||||||
|
result (ptk/update dv/zoom-to-fit state)]
|
||||||
|
(t/is (= (get-in result [:viewer-local :zoom-type]) :fit))
|
||||||
|
(t/is (number? (get-in result [:viewer-local :zoom])))))
|
||||||
|
|
||||||
|
(t/testing "index is zero with single frame (normal case)"
|
||||||
|
(let [state (base-state {:frames [{:selrect {:width 100 :height 100}}]
|
||||||
|
:index 0})
|
||||||
|
result (ptk/update dv/zoom-to-fit state)]
|
||||||
|
(t/is (= (get-in result [:viewer-local :zoom-type]) :fit))
|
||||||
|
(t/is (number? (get-in result [:viewer-local :zoom])))))
|
||||||
|
|
||||||
|
(t/testing "index within valid range with multiple frames"
|
||||||
|
(let [state (base-state {:frames [{:selrect {:width 100 :height 100}}
|
||||||
|
{:selrect {:width 200 :height 200}}]
|
||||||
|
:index 1})
|
||||||
|
result (ptk/update dv/zoom-to-fit state)]
|
||||||
|
(t/is (= (get-in result [:viewer-local :zoom-type]) :fit))
|
||||||
|
(t/is (number? (get-in result [:viewer-local :zoom]))))))
|
||||||
|
|
||||||
|
(t/deftest zoom-to-fill-clamps-out-of-bounds-index
|
||||||
|
(t/testing "index exceeds frame count"
|
||||||
|
(let [state (base-state {:frames [{:selrect {:width 100 :height 100}}]
|
||||||
|
:index 1})
|
||||||
|
result (ptk/update dv/zoom-to-fill state)]
|
||||||
|
(t/is (= (get-in result [:viewer-local :zoom-type]) :fill))
|
||||||
|
(t/is (number? (get-in result [:viewer-local :zoom])))))
|
||||||
|
|
||||||
|
(t/testing "index is zero with single frame (normal case)"
|
||||||
|
(let [state (base-state {:frames [{:selrect {:width 100 :height 100}}]
|
||||||
|
:index 0})
|
||||||
|
result (ptk/update dv/zoom-to-fill state)]
|
||||||
|
(t/is (= (get-in result [:viewer-local :zoom-type]) :fill))
|
||||||
|
(t/is (number? (get-in result [:viewer-local :zoom])))))
|
||||||
|
|
||||||
|
(t/testing "index within valid range with multiple frames"
|
||||||
|
(let [state (base-state {:frames [{:selrect {:width 100 :height 100}}
|
||||||
|
{:selrect {:width 200 :height 200}}]
|
||||||
|
:index 1})
|
||||||
|
result (ptk/update dv/zoom-to-fill state)]
|
||||||
|
(t/is (= (get-in result [:viewer-local :zoom-type]) :fill))
|
||||||
|
(t/is (number? (get-in result [:viewer-local :zoom]))))))
|
||||||
@ -3,6 +3,7 @@
|
|||||||
[cljs.test :as t]
|
[cljs.test :as t]
|
||||||
[frontend-tests.basic-shapes-test]
|
[frontend-tests.basic-shapes-test]
|
||||||
[frontend-tests.data.repo-test]
|
[frontend-tests.data.repo-test]
|
||||||
|
[frontend-tests.data.viewer-test]
|
||||||
[frontend-tests.data.workspace-colors-test]
|
[frontend-tests.data.workspace-colors-test]
|
||||||
[frontend-tests.data.workspace-texts-test]
|
[frontend-tests.data.workspace-texts-test]
|
||||||
[frontend-tests.helpers-shapes-test]
|
[frontend-tests.helpers-shapes-test]
|
||||||
@ -39,6 +40,7 @@
|
|||||||
(t/run-tests
|
(t/run-tests
|
||||||
'frontend-tests.basic-shapes-test
|
'frontend-tests.basic-shapes-test
|
||||||
'frontend-tests.data.repo-test
|
'frontend-tests.data.repo-test
|
||||||
|
'frontend-tests.data.viewer-test
|
||||||
'frontend-tests.data.workspace-colors-test
|
'frontend-tests.data.workspace-colors-test
|
||||||
'frontend-tests.data.workspace-texts-test
|
'frontend-tests.data.workspace-texts-test
|
||||||
'frontend-tests.helpers-shapes-test
|
'frontend-tests.helpers-shapes-test
|
||||||
@ -56,9 +58,9 @@
|
|||||||
'frontend-tests.tokens.logic.token-remapping-test
|
'frontend-tests.tokens.logic.token-remapping-test
|
||||||
'frontend-tests.tokens.style-dictionary-test
|
'frontend-tests.tokens.style-dictionary-test
|
||||||
'frontend-tests.tokens.token-errors-test
|
'frontend-tests.tokens.token-errors-test
|
||||||
|
'frontend-tests.tokens.workspace-tokens-remap-test
|
||||||
'frontend-tests.ui.ds-controls-numeric-input-test
|
'frontend-tests.ui.ds-controls-numeric-input-test
|
||||||
'frontend-tests.util-object-test
|
'frontend-tests.util-object-test
|
||||||
'frontend-tests.util-range-tree-test
|
'frontend-tests.util-range-tree-test
|
||||||
'frontend-tests.util-simple-math-test
|
'frontend-tests.util-simple-math-test
|
||||||
'frontend-tests.tokens.workspace-tokens-remap-test
|
|
||||||
'frontend-tests.worker-snap-test))
|
'frontend-tests.worker-snap-test))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user