w-flow-vue 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,119 +1,206 @@
1
- <template>
2
- <!-- 垂直選單: 改用w-threejs-vue同款組件WGroupIconCheck(:dir='vertical'), 最頂為設定鈕, 收合時僅顯示設定鈕, 展開才顯示其餘按鈕 -->
3
- <div
4
- :class="['vue-flow__panel', `vue-flow__panel--${position}`]"
5
- :style="panelStyle"
6
- >
7
- <!-- 淺色系配色(w-flow-vue圖台為白底, 沿用原Controls之#fefefe白底/深灰icon風格; 不設iconSize故用預設22, 按鈕寬=22+左右padding8=30px與w-threejs-vue選單一致) -->
8
- <!-- 全數為一般動作鈕(不用active態): 鎖頭點擊即切換上鎖/解鎖並換icon(比照fitView等), 不留固定灰底; value恆空故無任一鈕呈active -->
9
- <WGroupIconCheck
10
- :items="useItems"
11
- :dir="'vertical'"
12
- :iconColor="'#555'"
13
- :iconColorHover="'#222'"
14
- :iconColorFocus="'#222'"
15
- :iconColorActive="'#111'"
16
- :backgroundColor="'#fefefe'"
17
- :backgroundColorHover="'#f0f0f0'"
18
- :backgroundColorFocus="'#f0f0f0'"
19
- :backgroundColorActive="'#e4e4e4'"
20
- :seplineColor="'#e6e6e6'"
21
- :shadow="true"
22
- :tooltipTextFontSize="'0.7rem'"
23
- :value="[]"
24
- @click="onClickItem"
25
- ></WGroupIconCheck>
26
- </div>
27
- </template>
28
-
29
- <script>
30
- import WGroupIconCheck from 'w-component-vue/src/components/WGroupIconCheck.vue'
31
- import { mdiCogOutline, mdiMagnifyPlusOutline, mdiMagnifyMinusOutline, mdiFitToPageOutline, mdiLockOutline, mdiLockOpenVariantOutline } from '@mdi/js/mdi.js'
32
-
33
- export default {
34
- name: 'FlowControls',
35
- components: { WGroupIconCheck },
36
- props: {
37
- showZoom: { type: Boolean, default: true },
38
- showFitView: { type: Boolean, default: true },
39
- showInteractive: { type: Boolean, default: true },
40
- locked: { type: Boolean, default: false },
41
- position: { type: String, default: 'top-left' },
42
- //垂直選單向下偏移量(px), 供宿主避開其他左上角覆蓋元素(如變更儲存鈕), 比照w-threejs-vue之menuYShift
43
- menuYShift: { type: Number, default: 0 },
44
- },
45
- data() {
46
- return {
47
- //設定鈕展開狀態(收合時僅顯示設定鈕, 展開才顯示其餘按鈕), 比照w-threejs-vue
48
- useSetting: true,
49
- }
50
- },
51
- computed: {
52
- allItems() {
53
- let rs = [
54
- { id: 'setting', icon: mdiCogOutline, tooltip: 'Settings' },
55
- ]
56
- if (this.showZoom) {
57
- rs.push({ id: 'zoomIn', icon: mdiMagnifyPlusOutline, tooltip: 'Zoom In' })
58
- rs.push({ id: 'zoomOut', icon: mdiMagnifyMinusOutline, tooltip: 'Zoom Out' })
59
- }
60
- if (this.showFitView) {
61
- rs.push({ id: 'fitView', icon: mdiFitToPageOutline, tooltip: 'Fit View' })
62
- }
63
- if (this.showInteractive) {
64
- rs.push({ id: 'interactive', icon: this.locked ? mdiLockOutline : mdiLockOpenVariantOutline, tooltip: this.locked ? 'Unlock' : 'Lock' })
65
- }
66
- return rs
67
- },
68
- useItems() {
69
- //設定鈕恆在最頂; 收合時僅顯示設定鈕
70
- if (this.useSetting) {
71
- return this.allItems
72
- }
73
- return [this.allItems[0]]
74
- },
75
- panelStyle() {
76
- //向下偏移: top-*位置加margin-top, bottom-*位置加margin-bottom(margin於絕對定位元素上等同再位移該側)
77
- let s = 'z-index:5;'
78
- if (this.position.indexOf('bottom') >= 0) {
79
- s += `margin-bottom:${this.menuYShift}px;`
80
- }
81
- else {
82
- s += `margin-top:${this.menuYShift}px;`
83
- }
84
- return s
85
- },
86
- },
87
- methods: {
88
- onClickItem(item) {
89
- let id = (item && item.id) || ''
90
- if (id === 'setting') {
91
- this.useSetting = !this.useSetting
92
- }
93
- else if (id === 'zoomIn') {
94
- this.$emit('zoom-in')
95
- }
96
- else if (id === 'zoomOut') {
97
- this.$emit('zoom-out')
98
- }
99
- else if (id === 'fitView') {
100
- this.$emit('fit-view')
101
- }
102
- else if (id === 'interactive') {
103
- this.$emit('toggle-interactive')
104
- }
105
- },
106
- },
107
- }
108
- </script>
109
-
110
- <style scoped>
111
- .vue-flow__panel {
112
- position: absolute;
113
- z-index: 5;
114
- }
115
- .vue-flow__panel--top-left { top: 10px; left: 10px; }
116
- .vue-flow__panel--top-right { top: 10px; right: 10px; }
117
- .vue-flow__panel--bottom-left { bottom: 10px; left: 10px; }
118
- .vue-flow__panel--bottom-right { bottom: 10px; right: 10px; }
119
- </style>
1
+ <template>
2
+ <!-- 垂直選單: 改用w-threejs-vue同款組件WGroupIconCheck(:dir='vertical'), 最頂為設定鈕, 收合時僅顯示設定鈕, 展開才顯示其餘按鈕 -->
3
+ <div
4
+ v-if="cfg.useMenu"
5
+ :class="['vue-flow__panel', `vue-flow__panel--${cfg.menuPosition}`]"
6
+ :style="panelStyle"
7
+ >
8
+ <!-- 淺色系配色(w-flow-vue圖台為白底, 沿用原Controls之#fefefe白底/深灰icon風格; iconSize預設22, 按鈕寬=22+左右padding8=30px與w-threejs-vue選單一致) -->
9
+ <!-- 全數為一般動作鈕(不用active態): 鎖頭點擊即切換上鎖/解鎖並換icon(比照fitView等), 不留固定灰底; value恆空故無任一鈕呈active, 故不開放*ColorActive類選項 -->
10
+ <WGroupIconCheck
11
+ :items="useItems"
12
+ :dir="'vertical'"
13
+ :iconColor="cfg.menuIconColor"
14
+ :iconColorHover="cfg.menuIconColorHover"
15
+ :iconColorFocus="cfg.menuIconColorFocus"
16
+ :iconSize="cfg.menuIconSize"
17
+ :backgroundColor="cfg.menuBackgroundColor"
18
+ :backgroundColorHover="cfg.menuBackgroundColorHover"
19
+ :backgroundColorFocus="cfg.menuBackgroundColorFocus"
20
+ :seplineColor="cfg.menuSeparatorColor"
21
+ :shadow="cfg.menuShadow"
22
+ :tooltipTextColor="cfg.menuTooltipTextColor"
23
+ :tooltipTextFontSize="cfg.menuTooltipTextFontSize"
24
+ :tooltipBackgroundColor="cfg.menuTooltipBackgroundColor"
25
+ :value="[]"
26
+ @click="onClickItem"
27
+ ></WGroupIconCheck>
28
+ </div>
29
+ </template>
30
+
31
+ <script>
32
+ import WGroupIconCheck from 'w-component-vue/src/components/WGroupIconCheck.vue'
33
+ import { mdiCogOutline, mdiMagnifyPlusOutline, mdiMagnifyMinusOutline, mdiFitToPageOutline, mdiLockOutline, mdiLockOpenVariantOutline } from '@mdi/js/mdi.js'
34
+
35
+ //menuDef, 選單各項之預設值, 未被opt覆寫者沿用之
36
+ //注意: menuIconSize, menuTooltipTextColor, menuTooltipBackgroundColor原為WGroupIconCheck之預設值,
37
+ //因w-component-vue為浮動相依(^2.5.2), 子套件改預設會靜默改變本套件外觀, 故於此明寫鎖住現值
38
+ let menuDef = {
39
+
40
+ //整體
41
+ useMenu: true,
42
+ menuPosition: 'top-left',
43
+ menuYShift: 0,
44
+ useSetting: true,
45
+
46
+ //設定鈕(收合/展開)
47
+ menuSettingIcon: mdiCogOutline,
48
+ menuSettingTooltip: 'Settings',
49
+
50
+ //放大
51
+ useMenuItemZoomIn: true,
52
+ menuZoomInIcon: mdiMagnifyPlusOutline,
53
+ menuZoomInTooltip: 'Zoom In',
54
+
55
+ //縮小
56
+ useMenuItemZoomOut: true,
57
+ menuZoomOutIcon: mdiMagnifyMinusOutline,
58
+ menuZoomOutTooltip: 'Zoom Out',
59
+
60
+ //全圖
61
+ useMenuItemFitView: true,
62
+ menuFitViewIcon: mdiFitToPageOutline,
63
+ menuFitViewTooltip: 'Fit View',
64
+
65
+ //鎖定(雙態: 無後綴為未鎖態, Locked後綴為已鎖態)
66
+ useMenuItemLock: true,
67
+ menuLockIcon: mdiLockOpenVariantOutline,
68
+ menuLockTooltip: 'Lock',
69
+ menuLockIconLocked: mdiLockOutline,
70
+ menuLockTooltipLocked: 'Unlock',
71
+
72
+ //樣式
73
+ menuIconColor: '#555',
74
+ menuIconColorHover: '#222',
75
+ menuIconColorFocus: '#222',
76
+ menuIconSize: 22,
77
+ menuBackgroundColor: '#fefefe',
78
+ menuBackgroundColorHover: '#f0f0f0',
79
+ menuBackgroundColorFocus: '#f0f0f0',
80
+ menuSeparatorColor: '#e6e6e6',
81
+ menuShadow: true,
82
+ menuTooltipTextColor: 'white',
83
+ menuTooltipTextFontSize: '0.7rem',
84
+ menuTooltipBackgroundColor: 'rgba(60,60,60,0.75)',
85
+
86
+ }
87
+
88
+ //menuPositions, menuPosition之允許值, 不在其中者回退預設
89
+ let menuPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right']
90
+
91
+ /**
92
+ * 取單項設定值, 型別與menuDef同型且有效才採用, 否則回退menuDef
93
+ */
94
+ function pickMenuValue(k, v) {
95
+ let d = menuDef[k]
96
+ if (typeof d === 'string') {
97
+ return (typeof v === 'string' && v !== '') ? v : d
98
+ }
99
+ if (typeof d === 'boolean') {
100
+ return (typeof v === 'boolean') ? v : d
101
+ }
102
+ if (typeof d === 'number') {
103
+ return (typeof v === 'number' && isFinite(v)) ? v : d
104
+ }
105
+ return d
106
+ }
107
+
108
+ export default {
109
+ name: 'FlowControls',
110
+ components: { WGroupIconCheck },
111
+ props: {
112
+ //選單設定物件, 鍵值同menuDef, 由WFlowVue自opt原樣取出後傳入; 各鍵未給或型別不符即回退menuDef
113
+ menu: { type: Object, default: () => ({}) },
114
+ locked: { type: Boolean, default: false },
115
+ },
116
+ data() {
117
+ return {
118
+ //設定鈕展開狀態(收合時僅顯示設定鈕, 展開才顯示其餘按鈕), 比照w-threejs-vue; 初值取menu.useSetting
119
+ useSetting: pickMenuValue('useSetting', (this.menu || {}).useSetting),
120
+ }
121
+ },
122
+ computed: {
123
+ cfg() {
124
+ let m = this.menu || {}
125
+ let r = {}
126
+ for (let k in menuDef) {
127
+ r[k] = pickMenuValue(k, m[k])
128
+ }
129
+ //menuPosition另檢核列舉值
130
+ if (menuPositions.indexOf(r.menuPosition) < 0) {
131
+ r.menuPosition = menuDef.menuPosition
132
+ }
133
+ return r
134
+ },
135
+ allItems() {
136
+ let c = this.cfg
137
+ let rs = [
138
+ { id: 'setting', icon: c.menuSettingIcon, tooltip: c.menuSettingTooltip },
139
+ ]
140
+ if (c.useMenuItemZoomIn) {
141
+ rs.push({ id: 'zoomIn', icon: c.menuZoomInIcon, tooltip: c.menuZoomInTooltip })
142
+ }
143
+ if (c.useMenuItemZoomOut) {
144
+ rs.push({ id: 'zoomOut', icon: c.menuZoomOutIcon, tooltip: c.menuZoomOutTooltip })
145
+ }
146
+ if (c.useMenuItemFitView) {
147
+ rs.push({ id: 'fitView', icon: c.menuFitViewIcon, tooltip: c.menuFitViewTooltip })
148
+ }
149
+ if (c.useMenuItemLock) {
150
+ rs.push({ id: 'interactive', icon: this.locked ? c.menuLockIconLocked : c.menuLockIcon, tooltip: this.locked ? c.menuLockTooltipLocked : c.menuLockTooltip })
151
+ }
152
+ return rs
153
+ },
154
+ useItems() {
155
+ //設定鈕恆在最頂; 收合時僅顯示設定鈕
156
+ if (this.useSetting) {
157
+ return this.allItems
158
+ }
159
+ return [this.allItems[0]]
160
+ },
161
+ panelStyle() {
162
+ //向下偏移: top-*位置加margin-top, bottom-*位置加margin-bottom(margin於絕對定位元素上等同再位移該側)
163
+ //故menuYShift之語義為[正值使選單遠離其錨定邊]: top-*向下, bottom-*向上
164
+ let s = 'z-index:5;'
165
+ if (this.cfg.menuPosition.indexOf('bottom') >= 0) {
166
+ s += `margin-bottom:${this.cfg.menuYShift}px;`
167
+ }
168
+ else {
169
+ s += `margin-top:${this.cfg.menuYShift}px;`
170
+ }
171
+ return s
172
+ },
173
+ },
174
+ methods: {
175
+ onClickItem(item) {
176
+ let id = (item && item.id) || ''
177
+ if (id === 'setting') {
178
+ this.useSetting = !this.useSetting
179
+ }
180
+ else if (id === 'zoomIn') {
181
+ this.$emit('zoom-in')
182
+ }
183
+ else if (id === 'zoomOut') {
184
+ this.$emit('zoom-out')
185
+ }
186
+ else if (id === 'fitView') {
187
+ this.$emit('fit-view')
188
+ }
189
+ else if (id === 'interactive') {
190
+ this.$emit('toggle-interactive')
191
+ }
192
+ },
193
+ },
194
+ }
195
+ </script>
196
+
197
+ <style scoped>
198
+ .vue-flow__panel {
199
+ position: absolute;
200
+ z-index: 5;
201
+ }
202
+ .vue-flow__panel--top-left { top: 10px; left: 10px; }
203
+ .vue-flow__panel--top-right { top: 10px; right: 10px; }
204
+ .vue-flow__panel--bottom-left { bottom: 10px; left: 10px; }
205
+ .vue-flow__panel--bottom-right { bottom: 10px; right: 10px; }
206
+ </style>
@@ -1,69 +1,248 @@
1
- import { mount } from '@vue/test-utils'
2
- import Controls from '../src/components/ui/Controls.vue'
3
-
4
- describe('Controls', () => {
5
- test('renders all buttons by default', () => {
6
- const wrapper = mount(Controls)
7
- const buttons = wrapper.findAll('button')
8
- expect(buttons).toHaveLength(4) // zoom in, zoom out, fit view, lock
9
- })
10
-
11
- test('hides zoom buttons when showZoom=false', () => {
12
- const wrapper = mount(Controls, { propsData: { showZoom: false } })
13
- const buttons = wrapper.findAll('button')
14
- expect(buttons).toHaveLength(2) // fit view, lock
15
- })
16
-
17
- test('hides fit view button when showFitView=false', () => {
18
- const wrapper = mount(Controls, { propsData: { showFitView: false } })
19
- const buttons = wrapper.findAll('button')
20
- expect(buttons).toHaveLength(3) // zoom in, zoom out, lock
21
- })
22
-
23
- test('hides interactive button when showInteractive=false', () => {
24
- const wrapper = mount(Controls, { propsData: { showInteractive: false } })
25
- const buttons = wrapper.findAll('button')
26
- expect(buttons).toHaveLength(3) // zoom in, zoom out, fit view
27
- })
28
-
29
- test('emits zoom-in on click', () => {
30
- const wrapper = mount(Controls)
31
- const buttons = wrapper.findAll('button')
32
- buttons.at(0).trigger('click') // first button is zoom in
33
- expect(wrapper.emitted('zoom-in')).toBeTruthy()
34
- })
35
-
36
- test('emits zoom-out on click', () => {
37
- const wrapper = mount(Controls)
38
- const buttons = wrapper.findAll('button')
39
- buttons.at(1).trigger('click')
40
- expect(wrapper.emitted('zoom-out')).toBeTruthy()
41
- })
42
-
43
- test('emits fit-view on click', () => {
44
- const wrapper = mount(Controls)
45
- const buttons = wrapper.findAll('button')
46
- buttons.at(2).trigger('click')
47
- expect(wrapper.emitted('fit-view')).toBeTruthy()
48
- })
49
-
50
- test('emits toggle-interactive on click', () => {
51
- const wrapper = mount(Controls)
52
- const buttons = wrapper.findAll('button')
53
- buttons.at(3).trigger('click')
54
- expect(wrapper.emitted('toggle-interactive')).toBeTruthy()
55
- })
56
-
57
- test('applies position class', () => {
58
- const wrapper = mount(Controls, { propsData: { position: 'bottom-right' } })
59
- expect(wrapper.classes()).toContain('vue-flow__panel--bottom-right')
60
- })
61
-
62
- test('renders slot content', () => {
63
- const wrapper = mount(Controls, {
64
- slots: { default: '<button class="custom-btn">Custom</button>' },
65
- })
66
- expect(wrapper.find('.custom-btn').exists()).toBe(true)
67
- })
68
- })
69
-
1
+ import { mount } from '@vue/test-utils'
2
+ import Controls from '../src/components/ui/Controls.vue'
3
+ import WGroupIconCheck from 'w-component-vue/src/components/WGroupIconCheck.vue'
4
+
5
+ //Controls以WGroupIconCheck渲染, 單顆按鈕為[role="button"]之元素(非原生<button>)
6
+ const btns = (w) => w.findAll('[role="button"]')
7
+
8
+ //WGroupIconCheck之toggleState於$nextTick內才emit, 故點擊後需多刷幾輪microtask才看得到結果
9
+ const flush = async (w) => {
10
+ for (let i = 0; i < 3; i++) {
11
+ await w.vm.$nextTick()
12
+ }
13
+ }
14
+ //WButtonCircle之role="button"在最外層div, 但@click掛在內層之可點擊面(帶cursor:pointer)上,
15
+ //jsdom之trigger不會往下傳, 故點擊須打在該內層元素
16
+ const clickItem = async (w, i) => {
17
+ await btns(w).at(i).find('[style*="cursor: pointer"]').trigger('click')
18
+ await flush(w)
19
+ }
20
+
21
+ describe('Controls 預設(不給任何menu選項)', () => {
22
+ test('顯示5顆鈕: setting/zoomIn/zoomOut/fitView/lock', () => {
23
+ const w = mount(Controls)
24
+ expect(w.vm.allItems.map((v) => v.id)).toEqual(['setting', 'zoomIn', 'zoomOut', 'fitView', 'interactive'])
25
+ expect(btns(w)).toHaveLength(5)
26
+ w.destroy()
27
+ })
28
+
29
+ test('tooltip與icon為現行英文預設', () => {
30
+ const w = mount(Controls)
31
+ expect(w.vm.allItems.map((v) => v.tooltip)).toEqual(['Settings', 'Zoom In', 'Zoom Out', 'Fit View', 'Lock'])
32
+ w.destroy()
33
+ })
34
+
35
+ test('套用top-left位置class', () => {
36
+ const w = mount(Controls)
37
+ expect(w.classes()).toContain('vue-flow__panel--top-left')
38
+ w.destroy()
39
+ })
40
+ })
41
+
42
+ describe('Controls 事件', () => {
43
+ test('各鈕點擊送出對應事件', async () => {
44
+ const w = mount(Controls)
45
+ await clickItem(w, 1)
46
+ expect(w.emitted('zoom-in')).toBeTruthy()
47
+ await clickItem(w, 2)
48
+ expect(w.emitted('zoom-out')).toBeTruthy()
49
+ await clickItem(w, 3)
50
+ expect(w.emitted('fit-view')).toBeTruthy()
51
+ await clickItem(w, 4)
52
+ expect(w.emitted('toggle-interactive')).toBeTruthy()
53
+ w.destroy()
54
+ })
55
+
56
+ test('設定鈕點擊為收合/展開, 不送出事件', async () => {
57
+ const w = mount(Controls)
58
+ expect(w.vm.useItems).toHaveLength(5)
59
+ await clickItem(w, 0)
60
+ expect(w.vm.useItems).toHaveLength(1)
61
+ expect(w.vm.useItems[0].id).toBe('setting')
62
+ await clickItem(w, 0)
63
+ expect(w.vm.useItems).toHaveLength(5)
64
+ expect(w.emitted('zoom-in')).toBeFalsy()
65
+ expect(w.emitted('toggle-interactive')).toBeFalsy()
66
+ w.destroy()
67
+ })
68
+ })
69
+
70
+ describe('Controls 可見性(useMenu/useMenuItem*)', () => {
71
+ test('useMenu=false整列不渲染', () => {
72
+ const w = mount(Controls, { propsData: { menu: { useMenu: false } } })
73
+ expect(w.find('.vue-flow__panel').exists()).toBe(false)
74
+ w.destroy()
75
+ })
76
+
77
+ test('useMenuItemZoomIn=false僅隱藏放大鈕, 縮小鈕仍在', () => {
78
+ const w = mount(Controls, { propsData: { menu: { useMenuItemZoomIn: false } } })
79
+ expect(w.vm.allItems.map((v) => v.id)).toEqual(['setting', 'zoomOut', 'fitView', 'interactive'])
80
+ w.destroy()
81
+ })
82
+
83
+ test('useMenuItemZoomOut=false僅隱藏縮小鈕, 放大鈕仍在', () => {
84
+ const w = mount(Controls, { propsData: { menu: { useMenuItemZoomOut: false } } })
85
+ expect(w.vm.allItems.map((v) => v.id)).toEqual(['setting', 'zoomIn', 'fitView', 'interactive'])
86
+ w.destroy()
87
+ })
88
+
89
+ test('useMenuItemFitView=false隱藏全圖鈕', () => {
90
+ const w = mount(Controls, { propsData: { menu: { useMenuItemFitView: false } } })
91
+ expect(w.vm.allItems.map((v) => v.id)).toEqual(['setting', 'zoomIn', 'zoomOut', 'interactive'])
92
+ w.destroy()
93
+ })
94
+
95
+ test('useMenuItemLock=false隱藏鎖頭鈕', () => {
96
+ const w = mount(Controls, { propsData: { menu: { useMenuItemLock: false } } })
97
+ expect(w.vm.allItems.map((v) => v.id)).toEqual(['setting', 'zoomIn', 'zoomOut', 'fitView'])
98
+ w.destroy()
99
+ })
100
+
101
+ test('useSetting=false初始即收合', () => {
102
+ const w = mount(Controls, { propsData: { menu: { useSetting: false } } })
103
+ expect(w.vm.useItems).toHaveLength(1)
104
+ expect(w.vm.useItems[0].id).toBe('setting')
105
+ w.destroy()
106
+ })
107
+ })
108
+
109
+ describe('Controls 文字與icon覆寫', () => {
110
+ test('逐項覆寫tooltip', () => {
111
+ const w = mount(Controls, {
112
+ propsData: {
113
+ menu: {
114
+ menuSettingTooltip: '收合工具列',
115
+ menuZoomInTooltip: '放大',
116
+ menuZoomOutTooltip: '縮小',
117
+ menuFitViewTooltip: '全圖顯示',
118
+ menuLockTooltip: '鎖定',
119
+ },
120
+ },
121
+ })
122
+ expect(w.vm.allItems.map((v) => v.tooltip)).toEqual(['收合工具列', '放大', '縮小', '全圖顯示', '鎖定'])
123
+ w.destroy()
124
+ })
125
+
126
+ test('部分覆寫時未給者維持英文預設', () => {
127
+ const w = mount(Controls, { propsData: { menu: { menuZoomInTooltip: '放大' } } })
128
+ expect(w.vm.allItems.map((v) => v.tooltip)).toEqual(['Settings', '放大', 'Zoom Out', 'Fit View', 'Lock'])
129
+ w.destroy()
130
+ })
131
+
132
+ test('空字串回退預設, 不出現空白tooltip', () => {
133
+ const w = mount(Controls, { propsData: { menu: { menuZoomInTooltip: '' } } })
134
+ expect(w.vm.cfg.menuZoomInTooltip).toBe('Zoom In')
135
+ w.destroy()
136
+ })
137
+
138
+ test('型別不符回退預設', () => {
139
+ const w = mount(Controls, {
140
+ propsData: {
141
+ menu: {
142
+ menuZoomInTooltip: 123,
143
+ useMenuItemFitView: 'yes',
144
+ menuIconSize: 'big',
145
+ menuYShift: NaN,
146
+ },
147
+ },
148
+ })
149
+ expect(w.vm.cfg.menuZoomInTooltip).toBe('Zoom In')
150
+ expect(w.vm.cfg.useMenuItemFitView).toBe(true)
151
+ expect(w.vm.cfg.menuIconSize).toBe(22)
152
+ expect(w.vm.cfg.menuYShift).toBe(0)
153
+ w.destroy()
154
+ })
155
+
156
+ test('覆寫icon', () => {
157
+ const w = mount(Controls, { propsData: { menu: { menuZoomInIcon: 'M0 0 L1 1' } } })
158
+ expect(w.vm.allItems[1].icon).toBe('M0 0 L1 1')
159
+ w.destroy()
160
+ })
161
+ })
162
+
163
+ describe('Controls 鎖頭雙態', () => {
164
+ test('未鎖態取無後綴之icon與tooltip', () => {
165
+ const w = mount(Controls, { propsData: { locked: false, menu: { menuLockTooltip: '鎖定', menuLockTooltipLocked: '解除鎖定' } } })
166
+ expect(w.vm.allItems[4].tooltip).toBe('鎖定')
167
+ w.destroy()
168
+ })
169
+
170
+ test('已鎖態取Locked後綴之icon與tooltip', () => {
171
+ const w = mount(Controls, { propsData: { locked: true, menu: { menuLockTooltip: '鎖定', menuLockTooltipLocked: '解除鎖定' } } })
172
+ expect(w.vm.allItems[4].tooltip).toBe('解除鎖定')
173
+ w.destroy()
174
+ })
175
+
176
+ test('未給覆寫時雙態為Lock/Unlock且icon不同', () => {
177
+ const w0 = mount(Controls, { propsData: { locked: false } })
178
+ const w1 = mount(Controls, { propsData: { locked: true } })
179
+ expect(w0.vm.allItems[4].tooltip).toBe('Lock')
180
+ expect(w1.vm.allItems[4].tooltip).toBe('Unlock')
181
+ expect(w0.vm.allItems[4].icon).not.toBe(w1.vm.allItems[4].icon)
182
+ w0.destroy()
183
+ w1.destroy()
184
+ })
185
+ })
186
+
187
+ describe('Controls 位置與位移', () => {
188
+ test('四個menuPosition各自套用對應class', () => {
189
+ const poss = ['top-left', 'top-right', 'bottom-left', 'bottom-right']
190
+ poss.forEach((p) => {
191
+ const w = mount(Controls, { propsData: { menu: { menuPosition: p } } })
192
+ expect(w.classes()).toContain(`vue-flow__panel--${p}`)
193
+ w.destroy()
194
+ })
195
+ })
196
+
197
+ test('menuPosition非允許值回退top-left', () => {
198
+ const w = mount(Controls, { propsData: { menu: { menuPosition: 'middle' } } })
199
+ expect(w.classes()).toContain('vue-flow__panel--top-left')
200
+ w.destroy()
201
+ })
202
+
203
+ test('menuYShift於top-*加margin-top, 於bottom-*加margin-bottom', () => {
204
+ const wt = mount(Controls, { propsData: { menu: { menuPosition: 'top-left', menuYShift: 40 } } })
205
+ expect(wt.vm.panelStyle).toContain('margin-top:40px;')
206
+ const wb = mount(Controls, { propsData: { menu: { menuPosition: 'bottom-left', menuYShift: 40 } } })
207
+ expect(wb.vm.panelStyle).toContain('margin-bottom:40px;')
208
+ wt.destroy()
209
+ wb.destroy()
210
+ })
211
+ })
212
+
213
+ describe('Controls 樣式選項', () => {
214
+ test('未給樣式選項時鎖住現行值(不隨w-component-vue預設浮動)', () => {
215
+ const c = mount(Controls).vm.cfg
216
+ expect(c.menuIconColor).toBe('#555')
217
+ expect(c.menuIconColorHover).toBe('#222')
218
+ expect(c.menuIconColorFocus).toBe('#222')
219
+ expect(c.menuIconSize).toBe(22)
220
+ expect(c.menuBackgroundColor).toBe('#fefefe')
221
+ expect(c.menuBackgroundColorHover).toBe('#f0f0f0')
222
+ expect(c.menuBackgroundColorFocus).toBe('#f0f0f0')
223
+ expect(c.menuSeparatorColor).toBe('#e6e6e6')
224
+ expect(c.menuShadow).toBe(true)
225
+ expect(c.menuTooltipTextColor).toBe('white')
226
+ expect(c.menuTooltipTextFontSize).toBe('0.7rem')
227
+ expect(c.menuTooltipBackgroundColor).toBe('rgba(60,60,60,0.75)')
228
+ })
229
+
230
+ test('樣式選項可覆寫並下傳至WGroupIconCheck', () => {
231
+ const w = mount(Controls, {
232
+ propsData: {
233
+ menu: {
234
+ menuIconColor: '#111',
235
+ menuSeparatorColor: '#abcabc',
236
+ menuShadow: false,
237
+ menuTooltipTextFontSize: '1rem',
238
+ },
239
+ },
240
+ })
241
+ const g = w.findComponent(WGroupIconCheck)
242
+ expect(g.props('iconColor')).toBe('#111')
243
+ expect(g.props('seplineColor')).toBe('#abcabc')
244
+ expect(g.props('shadow')).toBe(false)
245
+ expect(g.props('tooltipTextFontSize')).toBe('1rem')
246
+ w.destroy()
247
+ })
248
+ })