# 流程_圖台互動

## 觸發

宿主以 `<WFlowVue :opt="opt">` 掛載圖台,`opt` 帶 `nodes` / `conns` / `width` / `height`。圖台初始化完成(`@init`)後,使用者以滑鼠與左上角垂直選單操作畫布、節點與連線。

本流程涵蓋圖台掛載後的所有互動與其可觀察結果,至使用者離開頁面為止。

## 重要流程

- **E2E-001** 圖台載入後點選單 fitView → 全部節點落入展示窗內(fit 以節點包絡計算,連線不納入——同 React Flow;極端轉折點可出窗,合約外),背景點陣正常。
- **E2E-002** 逐一將視窗置中於各節點 → 各形狀(矩形 / 菱形 / 橢圓 / 上下左右四向三角形)之外觀、字色、邊框、四邊連接點位置(`流程_互動契約.md` §4.1:形狀該邊之中點,三角形斜邊取斜邊中點)皆正確,其周邊連線亦正確繪出。
- **E2E-003** 未上鎖時滑鼠移入節點 → 節點出現 resize 把手與連接點;設定齒輪**不**出現(預設 `nodesSettingsTrigger='dblclick'`:雙擊直接開設定 popup、不顯示齒輪,見 E2E-037)。
- **E2E-004** 未上鎖時點擊節點 → 節點呈選取態(外框加粗 1px + 淡黑光暈 rgba(0,0,0,0.1);連線選取態同語義:線寬 +1px + 淡黑光暈),其餘節點不呈選取態。
- **E2E-005** 未上鎖時於節點本體按下左鍵並拖曳 → 節點座標隨游標移動,放開後停在放開位置,並發出 `update:nodes`。
- **E2E-006** 未上鎖時拖曳節點的 resize 把手 → 節點尺寸隨之改變,放開後發出 `update:nodes`;**該節點成為唯一選取(active)**——縮放為元素專屬操作,作用對象須與視覺選取一致。
- **E2E-007** 未上鎖時滑鼠移入連線 → 連線加深(hover 態);設定齒輪**不**出現(預設 `connsSettingsTrigger='dblclick'`)。
- **E2E-008** 經過上述 hover 與點擊互動後,將滑鼠移離並重新 fitView → 畫布回到靜止態,外觀與 E2E-001 一致(hover 態與選取態皆已解除,無殘留)。
- **E2E-009** 點擊帶 `name` 或 `description` 的連線 → 開啟資訊 popup,內容顯示 `name` 與 `description`。
- **E2E-010** 點擊未帶 `name`、`description`,且宿主未提供 `conn-popup` slot 的連線 → 不出現資訊 popup。
- **E2E-011** 雙擊節點本體 → 直接開啟節點設定表單(預設 dblclick 模式,無齒輪 icon),且節點座標不變(不得因點齒輪而觸發拖曳);**該節點成為唯一選取(active)**——先點 A 再點 B 之齒輪時,active 須轉移至 B,不得停留在 A。
- **E2E-012** 節點設定 popup 開啟中,雙擊某連線 label → 直接開啟該連線設定 popup; 節點 popup 關閉,連線 popup 開啟,畫面同時只存在一個設定表單。
- **E2E-013** 資訊 popup 開啟中,於連線轉折點按下左鍵 → 資訊 popup 關閉,且圖台不平移。
- **E2E-014** 拖曳連線轉折點 → 轉折點座標隨游標移動,拖曳過程中的連線路徑與放開後一致(放開瞬間不跳動),放開後發出 `conn-settings-update`。
- **E2E-015** 拖曳自動路由(step / smoothstep)連線所連接的節點 → 拖曳過程中的連線路徑與放開後一致(放開瞬間不跳動)。
- **E2E-016** 於空白畫布按下左鍵並拖曳 → 圖台平移,節點與連線相對位置不變,背景點陣隨之捲動。
- **E2E-017** 於左上角垂直選單按鈕上按下左鍵並拖曳 → 圖台不平移。
- **E2E-018** 點選單放大鈕 → 圖台以展示窗中心為固定點放大(非以左上角為錨點)。
- **E2E-019** 點選單縮小鈕 → 圖台以展示窗中心為固定點縮小。
- **E2E-020** 於畫布上滾動滾輪 → 圖台以游標位置為固定點縮放。
- **E2E-021** 點選單設定鈕 → 選單由 5 鈕收合為僅顯示設定鈕;再點一次展開回 5 鈕。
- **E2E-022** 點選單鎖頭鈕 → 進入上鎖態,鎖頭 icon 由開鎖切換為上鎖。
- **E2E-023** 上鎖態下滑鼠移入節點 → 不出現設定齒輪、不出現 resize 把手、不出現連接點。
- **E2E-024** 上鎖態下滑鼠移入連線 → 不出現連線設定齒輪。
- **E2E-025** 上鎖態下於節點本體按下左鍵並拖曳 → 節點座標不變。
- **E2E-026** 上鎖態下點擊節點 → 節點呈選取態(選取不受上鎖限制)。
- **E2E-027** 未上鎖時自某節點連接點拖曳至另一節點連接點 → 建立新連線,發出 `update:conns`;新 conn 為 `{ id, from: 出發節點, to: 落點節點, fromPosition: 出發邊, toPosition: 落點邊, markerTo: 'arrowclosed' }`——**邊持有自己兩端之方位**(`流程_互動契約.md` §4.2),to 端自動實心箭頭。
- **E2E-028** 按住 `Shift` 於空白畫布拖曳 → 出現選取框,框內節點呈選取態;**連線不參與框選複選**(連線為起訖節點錨點/轉折點推得之衍生物,不視為可被複選之項目),故框選一律清空連線選取。框選期間按住 Shift 即處於複選模式(E2E-033),故截圖中全部節點之把手/齒輪/縮放角隱藏。
- **E2E-029** 節點內容為可選文字(宿主 CSS `user-select:text`)且該文字**已有選取**時,於選取文字上按下左鍵拖曳 → 節點照常移動至目標位置;原生文字層 drag 不得接管(`dragstart` 不觸發)。(修正前真瀏覽器實測:原生 drag 接管、mousemove 斷流、節點僅移 8px 即凍結)
- **E2E-030** 同環境**無既有選取**時,於節點文字上按下左鍵拖曳 → 節點照常移動,且拖曳過程不形成文字選取(選取殘留即成為下一次拖曳觸發 E2E-029 病徵之來源)。
- **E2E-031** 於連線設定表單將 From Anchor 改為 Right → 該連線之出發端改自 from 節點右側連接點射出並改道(`conn.fromPosition === 'right'`);節點把手不動(四邊把手恆在,無把手隨之搬移)。
- **E2E-032** 建線拖曳中之落點可行性即時回饋(單一 case 之承接式 journey,多階段截圖):出發把手標記 origin;**出發節點之其他三把手**(自我連線)一律淡化並顯示 `not-allowed` 游標(不需 hover 判定即刻正確);hover 已有同向連線之他節點把手 → 該把手標 `invalid`(duplicate;danger 紅 ring + `not-allowed`),預覽線轉 danger 色;hover 自己節點之把手 → `invalid`(self);hover 合法他節點把手 → 標 `valid`(主題藍 ring + `crosshair`),預覽線轉主題藍實線;於不可連落點放開 → 不建線,且手勢暫態(`data-connect-*` 標記、出發節點 `data-connect-origin-node`、根 `vue-flow--connecting` class)全數清除。(對齊 React Flow/Vue Flow 之 connecting/valid 慣例)
- **E2E-033** 複選模式統一互動契約:按住複選鍵(預設 Shift)期間,全部節點統一隱藏設定齒輪、四角縮放把手、連出入把手(`visibility:hidden` + `pointer-events:none`,原位置點擊穿透至節點本體),邊之齒輪與轉折點同隱藏;已開之資訊/設定 popup 於進入模式時關閉,模式中所有 popup 開啟入口(含程式化 API)一律拒開;點節點本體=toggle 複選(先選 a 再 Shift+點 b → a 保留、b 加入),點邊不變更選取,既有連線選取不被 toggle 路徑清除;模式中不得啟動建線/縮放/轉折點拖曳,activate(sole-select)不作用;建線手勢進行中按住複選鍵時建線續行、落點把手不隱藏;放開後 affordance 恢復;Shift+dblclick 因兩次 click 而 toggle 兩次(淨零)後照發事件。鍵盤作用域:於 input/textarea/select/contenteditable 內按鍵不觸發畫布快捷鍵(複選鍵與 Delete);同頁多實例同時進入模式(全域行為,刻意)。(修正前:Shift+點 b 之齒輪/縮放角/把手各給不同反應——sole-select 無視複選鍵、popup 開關與否取決於各 handler 有無 stopPropagation,純屬事故;已裁定統一為複選模式)
- **E2E-034** 自節點**任一邊**之連接點按下左鍵拖曳建線(四把手對稱,契約見 `流程_互動契約.md` §4;單一 case 之承接式 journey,多階段截圖):靜止 hover 該把手放大且游標 `crosshair`;按下後出發把手標 origin、出發節點標 `data-connect-origin-node`,其其他三把手淡化並 `not-allowed`;hover 自己節點之另一把手 → `invalid`(self);hover 他節點之任一把手 → `valid` + `crosshair`,預覽線自出發把手(from 端,方位=出發邊)畫向游標;放開 → 建立連線且 `from` 為出發節點、`to` 為落點節點、`fromPosition`/`toPosition` 為兩把手所在邊,發出 `update:conns`。
- **E2E-035** 手勢生命週期與 popup 協調(契約 §5-§6):節點 A 資訊 popup 開啟中,自節點 B 之把手按下拉線 → A 之 popup 立即關閉;放開後於 B 四角按下縮放 → 已開 popup 亦關閉;拖曳節點 A 經過節點 B → B 不出現齒輪/四角/hover 陰影(手勢中非擁有者元素之 hover affordance 抑制),B 之把手游標為 `default`(非建線手勢中不承諾可出發),放開後 hover B 恢復。(修正前實測:把手/四角之 mousedown 帶 `.stop`,WPopup 掛在 window 之互斥關閉收不到,A 之 popup 整段手勢不關;拖曳經過 B 時 B 亮起齒輪、四角、陰影)
- **E2E-036** 雙向箭頭:demo 連線 e3-5 帶 `markerFrom:'arrow'`、`markerTo:'arrowclosed'`、`markerToSize:14`、`markerToFaceColor:'#ffa500'` → 路徑帶 `marker-start`/`marker-end`,各引用 `<defs>` 內同 id 之 `<marker>`(`流程_互動契約.md` §4.3):start 為線式(fill none、stroke=線色),end 為實心(fill `#ffa500`、`markerWidth=14`、`userSpaceOnUse`);於連線設定表單將 From Marker 改為 Arrow Closed、To Marker Size 改為 20 → conn 資料更新,路徑之 `marker-start` 改引用實心 marker、`marker-end` 改引用 `markerWidth=20` 之 marker,發出 `conn-settings-update`。(單一 case 兩階段截圖:初始 / 編輯後)
- **E2E-037** 設定入口方式(`opt.nodesSettingsTrigger` / `opt.connsSettingsTrigger` ∈ `hover | click | dblclick`,預設 `dblclick`):預設下 hover 節點不出齒輪;雙擊節點本體 → **直接開啟設定 popup**(不顯示齒輪 icon;錨區僅供定位),該節點成為唯一選取;點空白畫布 → popup 關閉;連線同(雙擊 label → 連線設定 popup);`click` 模式 → 單擊即開(資訊 popup 讓位);`hover` 模式 → 移入出現齒輪(節點右上角 / label 右側)、移出即收,點齒輪開設定(既有行為);連線 label 兩側空白**不可點**(無透明 hover 區,可點只有線本體與文字)。`dblclick` 模式下單擊之資訊 popup 延後 250ms 雙擊判定窗再開(瀏覽器於雙擊前必先派發 click,不得先閃現資訊 popup),期間雙擊即取消只開設定。(單一 case 兩階段截圖:節點 popup / 連線 popup)
- **E2E-038** 初始化 fit(`opt.fitViewOnInit` 預設 true):頁面載入後,元件先以隱藏狀態渲染並量測全部節點尺寸,再計算 fit(padding 50 螢幕像素、工具區側取讓位、上限 `zoomMax`)、寫入 viewport、解除隱藏,之後才發 `init`——使用者**看不到 fit 前之原點視口幀**(以 rAF 逐幀記錄:可見期間 viewport transform 唯一且非 `scale(1)`;pending 期間 visibility hidden);最終 viewport 等於 `viewport.computeFitView(可見節點包絡, 容器, {padding:50, maxZoom})`;全部節點落在畫布內;完成後根無 `vue-flow--pending`。其餘 case 之前置於 openPage 將視口重設為原點(setup 非 act),不受此預設影響。
- **E2E-039** 圖台尺寸變更(`opt.width` / `opt.height`):demo 按鈕(真實點擊)set width to 300 / set height to 300 → 容器實際變為 300×300;**舊圖台中心之內容於新圖台仍居中**(節點 1 先置中,變寬/變高後其中心與圖台中心誤差 <1.5px;zoom 不變)——地圖庫慣例(Leaflet `invalidateSize` pan:true);每次變更發 `resize` 事件,payload `{ width, height, oldWidth, oldHeight, viewport }`。
- **E2E-040** fit 留白契約(`opt.fitViewPadding`,CSS 像素,唯一權威):以寬受限網格(2920×1544 vs 容器 800×600)為前置,真實點擊工具區之 fitView 鈕 → 預設 50:**無任何節點與工具區矩形相交**、受限軸(左右)兩側邊距皆為 50、節點與工具區有間隙、邊距不隨 zoom 縮水(舊畫布座標語義於 zoom≈0.24 會縮成 ~12px);改 `fitViewPadding: 0` 再點 fitView → **受限軸貼邊(邊距 0)**,節點可進入工具區底下(明確選擇,不自動讓位——同 Leaflet/OpenLayers/React Flow)。
- **E2E-041** 設定表單之屬性分群顯隱(契約 §11):雙擊節點開啟設定 popup → 群數 4(Basic / Appearance / Text / Advanced),**僅 Basic 展開**(其三角形朝下、`aria-expanded="true"`,Name/Description 可見),其餘三群收合(三角形朝右、`aria-expanded="false"`,群內欄位不可見);真實點擊 `Appearance` **標題文字**(非三角形,驗整列可點)→ 該群展開且 Basic **仍保持展開**(各群獨立顯隱,非手風琴式互斥);展開前後 **popup 寬度不變**(收合內容仍參與寬度計算);再點一次 → 收合且 Basic 不受影響。連線設定 popup 同機制,群數 5(Basic / Path / Appearance / Arrows / Text),`Path` 群展開後含 Waypoints 子區塊。刪除鈕**不在任何群內**,恆顯示於表單底部。整條標題列可點(三角形本身、標題文字、列內空白皆可),且標題為 `<button>` 故可 Tab 聚焦並以 **Enter / Space** 切換;收合群內之控制項一律退出 Tab 序列。群標題列有底色且 `min-height:27px`(欄位列 18px)——只靠字重字級分不出分區;底色**貼齊 popup 左右邊緣**(full-bleed,inset 0)、無圓角,留白邊會讀成浮在中間的色塊。表單受 `opt.settingsPopupMaxHeight`(預設 400px)限高,連線表單全展開須**封頂於 400px 並於表單內捲動**,且出現捲動條後外框寬、`clientWidth` 與控制項右緣皆不變、無水平捲動。(單一 case 五階段截圖:節點預設 / 節點展開 Appearance / 節點全展開(封頂捲動)/ 連線預設 / 連線展開 Path)
- **E2E-042** 設定表單之深色主題適應(契約 §11「主題適應」):宿主經 `opt.settingsPopupBackgroundColor` / `settingsPopupTextColor` 指定深色 popup(setup,非 act)後,雙擊節點開設定 popup 並真實點擊展開 `Appearance` → 群標題底色**由疊黑改為疊白加亮**(`rgba(255,255,255,0.10)`,淺色主題為 `rgba(0,0,0,0.06)`)、三角形**改用淺色**(`rgba(255,255,255,0.72)`)、標題文字**繼承** `settingsPopupTextColor`;深色底若仍疊黑,標題帶會比背景更暗、三角形直接看不見。幾何契約不隨主題變動:full-bleed inset、縱向邊界間距、標題列 `min-height` 皆與淺色相同。(單一截圖:深色 popup 展開態)
- **E2E-043** 以色票改箭頭之填色與框色(契約 §4.3):demo 之 e3-5 為 `markerFrom='arrow'`(線式)/ `markerTo='arrowclosed'`(實心,`markerToFaceColor='#ffa500'`),初始框色跟隨線色。開連線設定 popup → 展開 `Arrows` → **可改矩陣**:線式端 Face Color 為 disabled 而 Edge Color 可改,實心端兩者皆可改。**走完整色票互動**(點色塊開面板 → 於 R/G/B 欄填值 → 按 Confirm):①填值後**未按確認前資料不得寫回**,點面板外關閉亦不得寫回(WColorSelect 曾有「開啟即觸發變更」之缺陷,於此鎖定);②改 To Marker Face Color → `conn.markerToFaceColor` 更新、marker 之 `fill` 隨之改變、**框色不受影響仍跟隨線色**、marker id 換新(規格不同不得共用 defs);③改 To Marker Edge Color → `conn.markerToEdgeColor` 更新、marker 之 `stroke` 改為該色且**不再等於線色**(此即本欄之目的),同時 Face 之值獨立保留;④線式端(From)之 Edge Color 亦生效(`stroke` 改變而 `fill` 恆 `none`)。(單一截圖:改色後之兩端箭頭)

## 未納入本流程之項目

- 多語系:本元件無 i18n,標準圖不分語系,不套用 `<lang>` 命名段。
- 鍵盤刪除(`opt.deleteKeyEnabled`)預設關閉,宿主未啟用時無此互動。
- 刪除確認(`opt.funConfirmDeleting`):套件**不內建二次確認 UI**——設定表單之刪除鈕按下即為刪除請求。宿主提供此 async callback 時,全部刪除入口(節點設定表單、連線設定表單、刪除鍵)一律先 `await` 其結果,**嚴格回傳 `true` 才真的刪除**(false / undefined / 其他值 / 拋錯一律不刪);payload 為 `{ nodes, conns, from }`,`conns` 含因節點被刪而連帶刪除之邊,`from` ∈ `'node-settings' | 'conn-settings' | 'delete-key'`。**確認期間畫面不得先行變動**(非樂觀刪除):等待 callback 回覆期間節點/連線一律保持原狀,也不預先發出 `update:nodes`/`update:conns`,故回 `false` 時不會出現「先消失又恢復」的閃爍;等待期間設定表單之刪除鈕呈 pending(`disabled` + 淡化,尺寸與文字不變故無版面跳動),避免慢流程被當成沒反應而連點。確認進行中之重入請求被擋;`await` 期間圖若已變動則僅刪除仍存在之目標。demo 未提供此 callback(故其刪除為即時),契約由單元測試 `unit-delete-confirm` 覆蓋(D1-D11),本流程不另設 e2e case。

## 標準圖命名與存放

- 存放於 `test/pics/`。
- 命名 `flow-E2E-NNN-<kebab>.png`;單一 case 有多個截圖點時,於 kebab 段後綴識別字(如 `flow-E2E-002-node-shape-1.png`)。
- 編號錨定本文件 bullet 順序,不隨測試執行順序或檔案排序而變。

## 標準圖對照

| Case | 標準圖 | 張數 | 現況 |
|---|---|---|---|
| E2E-001 | `flow-E2E-001-overview.png` | 1 | 已產製 |
| E2E-002 | `flow-E2E-002-node-shape-<id>.png` | 12 | 已產製 |
| E2E-003 | `flow-E2E-003-node-hovered.png` | 1 | 已產製 |
| E2E-004 | `flow-E2E-004-node-selected.png` | 1 | 已產製 |
| E2E-005 | `flow-E2E-005-node-dragged.png` | 1 | 已產製 |
| E2E-006 | `flow-E2E-006-node-resized.png` | 1 | 已產製 |
| E2E-007 | `flow-E2E-007-edge-hovered.png` | 1 | 已產製 |
| E2E-008 | `flow-E2E-008-idle-after-interaction.png` | 1 | 已產製 |
| E2E-009 | `flow-E2E-009-conn-info-popup.png` | 1 | 已產製 |
| E2E-010 | `flow-E2E-010-conn-no-info-popup.png` | 1 | 已產製 |
| E2E-011 | `flow-E2E-011-node-settings-form.png` | 1 | 已產製 |
| E2E-012 | `flow-E2E-012-popup-exclusive.png` | 1 | 已產製 |
| E2E-013 | `flow-E2E-013-waypoint-mousedown-closes-popup.png` | 1 | 已產製 |
| E2E-014 | `flow-E2E-014-waypoint-dragged.png` | 1 | 已產製 |
| E2E-015 | `flow-E2E-015-node-dragged-routing.png` | 1 | 已產製 |
| E2E-016 | `flow-E2E-016-panned.png` | 1 | 已產製 |
| E2E-017 | `flow-E2E-017-menu-drag-no-pan.png` | 1 | 已產製 |
| E2E-018 | `flow-E2E-018-zoom-in-center.png` | 1 | 已產製 |
| E2E-019 | `flow-E2E-019-zoom-out-center.png` | 1 | 已產製 |
| E2E-020 | `flow-E2E-020-wheel-zoom-cursor.png` | 1 | 已產製 |
| E2E-021 | `flow-E2E-021-menu-collapsed.png` | 1 | 已產製 |
| E2E-022 | `flow-E2E-022-locked-overview.png` | 1 | 已產製 |
| E2E-023 | `flow-E2E-023-locked-node-hovered.png` | 1 | 已產製 |
| E2E-024 | `flow-E2E-024-locked-edge-hovered.png` | 1 | 已產製 |
| E2E-025 | `flow-E2E-025-locked-node-drag-no-move.png` | 1 | 已產製 |
| E2E-026 | `flow-E2E-026-locked-node-selected.png` | 1 | 已產製 |
| E2E-027 | `flow-E2E-027-conn-created.png` | 1 | 已產製 |
| E2E-028 | `flow-E2E-028-box-selection.png` | 1 | 已產製 |
| E2E-029 | `flow-E2E-029-textdrag-preselected.png` | 1 | 已產製 |
| E2E-030 | `flow-E2E-030-textdrag-no-selection-forms.png` | 1 | 已產製 |
| E2E-031 | `flow-E2E-031-from-anchor-right.png` | 1 | 已產製 |
| E2E-032 | `flow-E2E-032-connect-feedback-invalid.png` / `flow-E2E-032-connect-feedback-valid.png` | 2 | 已產製 |
| E2E-033 | `flow-E2E-033-multiselect-mode.png` | 1 | 已產製 |
| E2E-034 | `flow-E2E-034-any-side-origin-invalid.png` / `flow-E2E-034-any-side-origin-valid.png` | 2 | 已產製 |
| E2E-035 | `flow-E2E-035-drag-no-hover-pollution.png` | 1 | 已產製 |
| E2E-036 | `flow-E2E-036-bidirectional-arrows-initial.png` / `flow-E2E-036-bidirectional-arrows-edited.png` | 2 | 已產製 |
| E2E-037 | `flow-E2E-037-settings-trigger-node.png` / `flow-E2E-037-settings-trigger-edge.png` | 2 | 已產製 |
| E2E-038 | `flow-E2E-038-init-fit.png` | 1 | 已產製 |
| E2E-039 | `flow-E2E-039-canvas-resize.png` | 1 | 已產製 |
| E2E-040 | `flow-E2E-040-fit-padding.png` | 1 | 已產製 |
| E2E-041 | `flow-E2E-041-settings-groups-node-default.png` / `-node-appearance.png` / `-node-expanded.png` / `-conn-default.png` / `-conn-path.png` | 5 | 已產製 |
| E2E-042 | `flow-E2E-042-settings-groups-dark.png` | 1 | 已產製 |
| E2E-043 | `flow-E2E-043-marker-face-edge-color.png` | 1 | 已產製 |

合計 62 張:37 個 case 各 1 張,E2E-002 佔 12 張,E2E-032 / E2E-034(invalid hover / valid hover)、E2E-036(初始 / 編輯後)與 E2E-037(節點 / 連線)各佔 2 張,E2E-041 佔 5 張。

## 語意斷言與 pixel baseline 之分工

pixel baseline 為補強層,不得作為唯一驗證。每個 case 均須先有語意斷言,對應本文件該條 bullet 之可觀察陳述:

| Case | 語意斷言(主) |
|---|---|
| E2E-003 | 齒輪不存在(預設 dblclick)、resize 把手數 > 0、連接點數 > 0 |
| E2E-004 | 該節點 id 在 `selectedNodes` 內,其餘不在 |
| E2E-005 | 拖曳後 `node.position` 等於起始座標加位移量;`update:nodes` 已發出 |
| E2E-006 | 縮放後 `node.width` / `node.height` 改變;`update:nodes` 已發出;該節點 id 為 `selectedNodes` 唯一成員 |
| E2E-007 | 連線帶 `--hovered` class;連線齒輪不存在(預設 dblclick) |
| E2E-008 | 無 hover 態元素(齒輪/把手/連接點皆不存在);`selectedNodes` 為空 |
| E2E-009 | popup 內文含該連線之 `name` 與 `description` |
| E2E-010 | 資訊 popup 不存在 |
| E2E-011 | 設定表單存在;`node.position` 與雙擊前相同;該節點 id 為 `selectedNodes` 唯一成員 |
| E2E-012 | 畫面上設定表單數量為 1;節點設定 popup 已不存在 |
| E2E-013 | 資訊 popup 已不存在;`viewport.x` / `viewport.y` 與按下前相同 |
| E2E-014 | 拖曳中之路徑 `d` 與放開後之路徑 `d` 相同;`conn.points` 已更新 |
| E2E-015 | 同 E2E-014 之路徑比對 |
| E2E-016 | `viewport.x` / `viewport.y` 位移量等於滑鼠位移量 |
| E2E-017 | `viewport.x` / `viewport.y` 與拖曳前相同 |
| E2E-018 | `viewport.zoom` 變為原值 ×1.2;展示窗中心對應之內容座標不變 |
| E2E-019 | `viewport.zoom` 變為原值 ÷1.2;展示窗中心對應之內容座標不變 |
| E2E-020 | `viewport.zoom` 改變;游標位置對應之內容座標不變 |
| E2E-021 | 收合後選單按鈕數為 1;展開後為 5 |
| E2E-022 | `locked === true` |
| E2E-023 | 齒輪不存在、resize 把手數為 0、連接點數為 0 |
| E2E-024 | 連線齒輪不存在 |
| E2E-025 | `node.position` 與拖曳前相同 |
| E2E-026 | 該節點 id 在 `selectedNodes` 內 |
| E2E-027 | `conns` 新增一筆,其 `from` / `to` 為該兩節點,`fromPosition` / `toPosition` 為出發邊 / 落點邊,`markerTo === 'arrowclosed'` 且路徑帶 `marker-end`;`update:conns` 已發出 |
| E2E-028 | 框內節點 id 皆在 `selectedNodes` 內,框外者不在;`selectedConns` 為空 |
| E2E-029 | `node.position` 位移等於拖曳量;頁面 `dragstart` 計數為 0 |
| E2E-030 | `node.position` 位移等於拖曳量;拖曳結束後 `window.getSelection()` 為空字串 |
| E2E-031 | `conn.fromPosition === 'right'`;該邊路徑 d 改變;from 節點仍為四把手且 bottom 把手 inline style 不變;`conn-settings-update` 已發出 |
| E2E-032 | 出發把手 `data-connect-role="origin"`;出發節點之其他三把手 computed `opacity < 0.5` 且 `cursor: not-allowed`;hover 已有同向邊之他節點把手 `data-connect-status="invalid"` 且 computed `cursor: not-allowed`,預覽線 class 含 `--invalid`;hover 自己節點把手為 `invalid`;hover 合法把手為 `valid` 且 `cursor: crosshair`,預覽線 class 含 `--valid`;於不可連落點放開後 `conns` 長度不變、`data-connect-*` 標記數為 0、根元素無 `vue-flow--connecting`、無 `data-connect-origin-node` |
| E2E-034 | 靜止時 left 把手 computed `cursor: crosshair`;按下後出發節點 `data-connect-origin-node`、出發把手 origin;自己節點其他把手 `opacity < 0.5` 且 `cursor: not-allowed`;hover 自己 top → `invalid`;hover 他節點 right → `valid` 且 `cursor: crosshair`;放開後 `conns` 增一且 `{from: 出發節點, to: 落點節點, fromPosition: 'left', toPosition: 'right'}`;`update:conns` 已發出 |
| E2E-035 | A 之 `infoPopupShow` 於 B 把手 mousedown 後為 false;於 B 四角 mousedown 後為 false;拖曳 A 經過 B 時 B 之齒輪 computed `opacity` 為 0、四角 `opacity` 為 0、B 無 hover 陰影(`box-shadow: none`)、B 把手 computed `cursor: default`;放開後 hover B 齒輪 `opacity` 為 1 |
| E2E-037 | 預設:hover 後 `.vue-flow__node-settings` 不存在;dblclick 後設定表單數 1 且齒輪 icon computed `visibility: hidden`(或不存在)、`selectedNodes` 為該節點;點空白後表單數 0;連線 dblclick label 後連線表單數 1 且齒輪不可見;`click` 模式單擊後表單數 1;`hover` 模式移入齒輪 `visibility: visible`、移出不存在;點 label 右側 25px 後 `selectedConns` 為空 |
| E2E-038 | rAF 記錄:可見幀之 transform 集合大小 1、首個可見幀非 `scale(1)` 且非 pending、pending 幀皆 hidden;`vm.viewport` ≈ `computeFitView(nodesBounds(nodes, internals, defNode), 容器 rect, {padding: 50, maxZoom})`(誤差 <1e-6);`viewPending=false` 且根無 `vue-flow--pending`;每個 `.vue-flow__node` bounding box 落在 `.vue-flow` 內 |
| E2E-039 | 容器 rect 300×300;節點 1 bounding box 中心與 `.vue-flow` rect 中心距離 <1.5px(變寬後與變高後各驗一次);`vm.viewport.zoom` 前後相等;`resize` 事件 2 次且 payload `{width,height,oldWidth,oldHeight,viewport}` 值正確 |
| E2E-040 | 預設 50:無任一 `.vue-flow__node` 之 bounding box 與 `.vue-flow__panel` 相交、左右邊距皆 ≈ 50(誤差 <1px)、左邊距 − 面板右緣 > 5px、右邊距 > 40(CSS 像素語義);`fitViewPadding: 0` 後再 fit:左右邊距 ≈ 0(貼邊)且與面板相交之節點 ≥ 1(契約明文之明確選擇) |
| E2E-041 | 節點表單群標題依序 `['Basic','Appearance','Text','Advanced']`、連線表單 `['Basic','Path','Appearance','Arrows','Text']`;初始各表單 `aria-expanded="true"` 僅一個且為 Basic;收合群之 panel 帶 `--closed` 且其內 `<label>` 之 computed `visibility === 'hidden'` 且佔位高度為 0(不可見;因收合以 `visibility:hidden + height:0` 實作,`offsetParent` 仍非 null,不可用其判定);**三角形之 computed `transform`**——收合為未旋轉(`none` 或單位矩陣)、展開為 rotate(90deg) 之矩陣(驗真正朝向,非只驗 class);**三角形 computed `color === rgb(102,102,102)`**(`#666`;對相鄰之標題底色 5.04:1,WCAG 1.4.11 要求 ≥3:1 且須以相鄰色計算;此色差 pixel baseline 抓不到);每群標題按鈕之 `closest('[role="heading"]')` 存在且 `aria-level === '3'`;點 Appearance 標題文字後該群 `aria-expanded` 轉 `true` 而 Basic 仍為 `true`(展開數 2);展開前後表單 `getBoundingClientRect().width` 差 <0.5px;再點該標題 → 回到 `false` 且 Basic 仍 `true`;**點三角形本身**與**點標題列右側空白處**皆能展開對應群;群標題可被 `focus()` 且 `document.activeElement` 為該標題,**Enter 收合、Space 重新展開**;收合群內所有 `input/select/button/[tabindex]` 之 computed `visibility` 皆為 `hidden`(不可聚焦);**標題列 computed `backgroundColor === rgba(0,0,0,0.06)` 且 `minHeight === '27px'`、其高度 ≥ 欄位列高度 + 5px**(視覺分區);**每個**標題列(非只第一個)之 full-bleed inset 左右皆 <0.6px、`borderRadius === '0px'`;標題文字與欄位標籤左緣差 <0.6px;label 與控制項 computed `fontSize` 等於表單 root 之 `fontSize`(字級單一來源);表單 computed `maxHeight === '400px'` 且 `overflowY === 'auto'`、`overflowX === 'hidden'`,連線表單全展開後 `getBoundingClientRect().height` ≈ 400(誤差 <1px)、`scrollHeight > clientHeight`、無水平捲動(`scrollWidth <= clientWidth`)、且外框寬 / `clientWidth` / 控制項右緣 inset 與捲動前相差 <0.5px;`.vue-flow__delete-area` 之 `closest('.vue-flow__settings-group') === null`、為表單最後一個子元素、其 full-bleed inset 左右皆 <0.6px(與群標題同一邊界),且刪除鈕右緣與輸入控制項右緣相差 <0.6px;**縱向邊界間距**(群↔群、末群↔刪除線)實測皆 <0.6px;節點表單四群全展開後亦 `height` ≈ 400、`scrollHeight > clientHeight`、寬度與邊界間距不變 |
| E2E-042 | 深色 popup 下:標題列 computed `backgroundColor === rgba(255,255,255,0.1)`(非淺色主題之 `rgba(0,0,0,0.06)`);每群三角形 computed `color === rgba(255,255,255,0.72)`;群標題文字 computed `color` 等於 `opt.settingsPopupTextColor`(`rgb(234,234,234)`);full-bleed inset 與縱向邊界間距皆 <0.6px、標題列 `minHeight === '27px'`(幾何契約不隨主題變動) |
| E2E-043 | 初始 marker `fill === '#ffa500'` 且 `stroke === 線色`;可改矩陣——`From Marker Face Color` 之 `.vue-flow__field` 帶 `--disabled` 而 `From Marker Edge Color` 不帶,`To Marker` 兩者皆不帶;色票填入 R/G/B 後**未按 Confirm 前** `conn.markerToFaceColor` 仍為 `#ffa500`,點面板外關閉後亦然;按 Confirm 後該鍵 === `rgba(12, 34, 56, 1)`、marker `fill` 同值、`stroke` 仍 === 線色、marker id 與改前不同;再改 Edge Color 後 `conn.markerToEdgeColor` === `rgba(200, 30, 90, 1)`、marker `stroke` 同值且 **≠ 線色**、`fill` 保留前次值;線式端改 Edge Color 後其 marker `stroke` 為所選色而 `fill === 'none'`;已發出 `conn-settings-update` |
| E2E-036 | 路徑 `marker-start`/`marker-end` 各為 `url(#id)` 且 `<marker id>` 存在;end marker `markerWidth=14`、path fill `#ffa500`、`markerUnits=userSpaceOnUse`;start marker path fill `none`;表單改 From Marker=Arrow Closed、To Marker Size=20 後 conn 資料更新、`marker-start` 引用之 marker path 為閉合(以 `z` 結尾)、`marker-end` 引用之 marker `markerWidth=20`;`conn-settings-update` 已發出 |
| E2E-033 | 點節點開 popup 後按住 Shift:`infoPopupShow` 轉 false;齒輪/縮放/把手 computed `visibility: hidden` 且 `pointer-events: none`;把手原位置 `elementFromPoint` 不落在把手;Shift+點另一節點 → `selectedNodes` 含兩者且不開 popup;放開 Shift 後 hover 之齒輪/把手 computed `visibility: visible` |

E2E-001 與 E2E-002 為純外觀 case,無對應語意陳述,以 pixel baseline 為主要驗證。

## 測試前置

- E2E-009 / E2E-010 需要「帶 `name` 與 `description` 之連線」與「兩者皆無之連線」各一。
- E2E-013 / E2E-014 需要帶 `points`(強制轉折點)之連線。
- E2E-015 需要 `type` 為 `step` 或 `smoothstep` 且未帶 `points` 之連線。

上述前置屬 setup 階段,得以直接設定 `opt` 資料達成;進入 act 階段後一律走真實滑鼠與鍵盤操作。
