@operato/menu 10.7.0 → 10.8.0
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.
- package/CHANGELOG.md +47 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/menu-active.d.ts +89 -0
- package/dist/src/menu-active.js +115 -0
- package/dist/src/menu-active.js.map +1 -0
- package/dist/src/menu-mode.d.ts +98 -0
- package/dist/src/menu-mode.js +100 -0
- package/dist/src/menu-mode.js.map +1 -0
- package/dist/src/menu-portrait-styles.d.ts +27 -0
- package/dist/src/menu-portrait-styles.js +516 -92
- package/dist/src/menu-portrait-styles.js.map +1 -1
- package/dist/src/menu-privilege.d.ts +34 -0
- package/dist/src/menu-privilege.js +22 -0
- package/dist/src/menu-privilege.js.map +1 -0
- package/dist/src/ox-menu-part.d.ts +100 -3
- package/dist/src/ox-menu-part.js +220 -82
- package/dist/src/ox-menu-part.js.map +1 -1
- package/dist/src/ox-menu-portrait.d.ts +158 -3
- package/dist/src/ox-menu-portrait.js +511 -13
- package/dist/src/ox-menu-portrait.js.map +1 -1
- package/dist/src/types.d.ts +38 -1
- package/dist/src/types.js.map +1 -1
- package/dist/test/menu-active.test.d.ts +1 -0
- package/dist/test/menu-active.test.js +131 -0
- package/dist/test/menu-active.test.js.map +1 -0
- package/dist/test/menu-mode.test.d.ts +1 -0
- package/dist/test/menu-mode.test.js +108 -0
- package/dist/test/menu-mode.test.js.map +1 -0
- package/dist/test/menu-privilege.test.d.ts +1 -0
- package/dist/test/menu-privilege.test.js +94 -0
- package/dist/test/menu-privilege.test.js.map +1 -0
- package/dist/test/ox-menu-portrait.test.d.ts +1 -0
- package/dist/test/ox-menu-portrait.test.js +339 -0
- package/dist/test/ox-menu-portrait.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/test/menu-active.test.ts +162 -0
- package/test/menu-mode.test.ts +135 -0
- package/test/menu-privilege.test.ts +138 -0
- package/test/ox-menu-portrait.test.ts +431 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { fixture, expect } from '@open-wc/testing'
|
|
3
|
+
|
|
4
|
+
import '../src/ox-menu-portrait.js'
|
|
5
|
+
import { OxMenuPortrait } from '../src/ox-menu-portrait.js'
|
|
6
|
+
import { Menu } from '../src/types.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 왼쪽 메뉴가 **모양 표대로 그려지고 움직이나.**
|
|
10
|
+
*
|
|
11
|
+
* `menu-mode.test.ts` 는 표 자체를 못박는다. 이 시험은 그 표가 **실제로 배선되어 있나**를 본다 —
|
|
12
|
+
* 순수 함수가 맞아도 컴포넌트가 그것을 안 물어보면 화면은 그대로 깨진다. 실제로 그랬다:
|
|
13
|
+
* `drawer` 를 만들고 넘기지 않아 거동이 하나도 바뀌지 않았다.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const 메뉴: Menu[] = [
|
|
17
|
+
{
|
|
18
|
+
name: 'my-work',
|
|
19
|
+
label: '내 일',
|
|
20
|
+
path: 'todo-list',
|
|
21
|
+
icon: 'inbox',
|
|
22
|
+
badge: () => 7,
|
|
23
|
+
menus: [
|
|
24
|
+
{ name: 'todo', label: '할 일', path: 'todo-list', badge: () => 3 },
|
|
25
|
+
{ name: 'done', label: '완료', path: 'done-list' }
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'work',
|
|
30
|
+
label: '작업',
|
|
31
|
+
path: 'job-order-list',
|
|
32
|
+
menus: [{ name: 'job-order', label: '작업지시', path: 'job-order-list' }]
|
|
33
|
+
},
|
|
34
|
+
/* 하위가 없는 최상위 — 「묶음이냐 끝이냐」를 가리는 시험에 쓴다. */
|
|
35
|
+
{ name: 'maturity', label: '성숙도', path: 'maturity-page', icon: 'insights' }
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
async function 세운다(props: Partial<OxMenuPortrait> = {}) {
|
|
39
|
+
const el = (await fixture(html`<ox-menu-portrait></ox-menu-portrait>`)) as OxMenuPortrait
|
|
40
|
+
|
|
41
|
+
el.menus = 메뉴
|
|
42
|
+
el.activeTopLevel = 메뉴[0]
|
|
43
|
+
el.activeMenu = 메뉴[0].menus![0]
|
|
44
|
+
Object.assign(el, props)
|
|
45
|
+
await el.updateComplete
|
|
46
|
+
|
|
47
|
+
return el
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 누르고, **컴포넌트가 이동을 막았나**를 돌려준다.
|
|
53
|
+
*
|
|
54
|
+
* ── 왜 이렇게 하나 ──────────────────────────────────────────────────────────
|
|
55
|
+
* 그냥 dispatch 하면 브라우저가 `<a href>` 의 기본 동작을 실행해 **시험 페이지가 실제로
|
|
56
|
+
* 이동한다.** 그래서 러너가 멈췄다(404 job-order-list).
|
|
57
|
+
*
|
|
58
|
+
* document 에서 받아 막으려 했더니 안 됐다 — 어떤 가지는 `stopPropagation` 을 하므로 거기까지
|
|
59
|
+
* 오지 않는다. 그러면 「막았다」를 「안 막았다」로 읽는다.
|
|
60
|
+
*
|
|
61
|
+
* 그래서 누르기 직전에 **href 를 뗀다.** 컴포넌트는 그 속성을 보지 않고 메뉴 객체의 `path` 로
|
|
62
|
+
* 판단하므로 판정이 바뀌지 않고, 브라우저는 갈 곳이 없어 움직이지 않는다.
|
|
63
|
+
*/
|
|
64
|
+
function 눌러본다(el: OxMenuPortrait, target: HTMLElement) {
|
|
65
|
+
const anchor = target.closest('a')
|
|
66
|
+
const href = anchor?.getAttribute('href')
|
|
67
|
+
anchor?.removeAttribute('href')
|
|
68
|
+
|
|
69
|
+
const ev = new MouseEvent('click', { bubbles: true, composed: true, cancelable: true })
|
|
70
|
+
target.dispatchEvent(ev)
|
|
71
|
+
|
|
72
|
+
if (href !== null && href !== undefined) {
|
|
73
|
+
anchor?.setAttribute('href', href)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return ev.defaultPrevented
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const 최상위들 = (el: OxMenuPortrait) =>
|
|
80
|
+
Array.from(el.renderRoot.querySelectorAll('li[top]')) as HTMLElement[]
|
|
81
|
+
|
|
82
|
+
const 최상위 = (el: OxMenuPortrait, name: string) =>
|
|
83
|
+
최상위들(el).find(li => (li as any).menu?.name === name)!
|
|
84
|
+
|
|
85
|
+
describe('ox-menu-portrait', () => {
|
|
86
|
+
describe('최상위와 하위의 표시는 뜻이 다르다', () => {
|
|
87
|
+
it('열린 묶음은 active, 지금 페이지는 current', async () => {
|
|
88
|
+
const el = await 세운다()
|
|
89
|
+
const top = 최상위(el, 'my-work')
|
|
90
|
+
|
|
91
|
+
expect(top.hasAttribute('active'), '하위가 펼쳐져 있다').to.be.true
|
|
92
|
+
expect(top.hasAttribute('current'), '최상위가 지금 페이지는 아니다').to.be.false
|
|
93
|
+
|
|
94
|
+
const kid = top.querySelector(':scope > ul > li')!
|
|
95
|
+
expect(kid.hasAttribute('current'), '지금 페이지는 이 하위다').to.be.true
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('최상위에만 top 이 붙는다 — 하위도 active 를 달기 때문이다', async () => {
|
|
99
|
+
const el = await 세운다()
|
|
100
|
+
const kid = 최상위(el, 'my-work').querySelector(':scope > ul > li')!
|
|
101
|
+
|
|
102
|
+
expect(kid.hasAttribute('top')).to.be.false
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('하위를 어디에 여나', () => {
|
|
107
|
+
/* 상태를 손으로 넣지 않고 **누른다** — 실제 경로가 그것이다. */
|
|
108
|
+
const 최상위를누른다 = async (el: OxMenuPortrait, name: string) => {
|
|
109
|
+
눌러본다(el, 최상위(el, name).querySelector('a')!)
|
|
110
|
+
await el.updateComplete
|
|
111
|
+
await el.updateComplete
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
it('기존 메뉴 · 펼친 레일 · 서랍은 그 자리다 — 팝업이 없다', async () => {
|
|
115
|
+
for (const props of [{}, { rail: true }, { drawer: true }]) {
|
|
116
|
+
const el = await 세운다(props)
|
|
117
|
+
await 최상위를누른다(el, 'work')
|
|
118
|
+
|
|
119
|
+
expect(el.renderRoot.querySelector('.railpop'), JSON.stringify(props)).to.be.null
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('접힌 레일에서만 팝업이 뜬다', async () => {
|
|
124
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
125
|
+
await 최상위를누른다(el, 'work')
|
|
126
|
+
|
|
127
|
+
expect(el.renderRoot.querySelector('.railpop')).to.exist
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('서랍은 레일·접힘을 함께 켜도 팝업이 아니다', async () => {
|
|
131
|
+
const el = await 세운다({ rail: true, collapsed: true, drawer: true })
|
|
132
|
+
await 최상위를누른다(el, 'work')
|
|
133
|
+
|
|
134
|
+
expect(el.renderRoot.querySelector('.railpop')).to.be.null
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('최상위를 누르면', () => {
|
|
139
|
+
const 누른다 = (el: OxMenuPortrait, name: string) => 눌러본다(el, 최상위(el, name).querySelector('a')!)
|
|
140
|
+
|
|
141
|
+
it('늘 보이는 메뉴에서는 이동을 막지 않는다', async () => {
|
|
142
|
+
const el = await 세운다({ rail: true })
|
|
143
|
+
expect(누른다(el, 'work'), '막으면 대표 화면으로 갈 수 없다').to.be.false
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('서랍에서는 이동을 막고 펼치기만 한다', async () => {
|
|
147
|
+
const el = await 세운다({ drawer: true })
|
|
148
|
+
let 올라온것: any = 'not-fired'
|
|
149
|
+
el.addEventListener('active-toplevel', (e: Event) => (올라온것 = (e as CustomEvent).detail))
|
|
150
|
+
|
|
151
|
+
expect(누른다(el, 'work'), '이동하면 서랍이 닫혀 나머지 하위를 볼 기회가 없다').to.be.true
|
|
152
|
+
expect(올라온것?.name).to.equal('work')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('접힌 레일에서는 팝업을 띄운다', async () => {
|
|
156
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
157
|
+
const 막았나 = 누른다(el, 'work')
|
|
158
|
+
await el.updateComplete
|
|
159
|
+
|
|
160
|
+
expect(막았나).to.be.true
|
|
161
|
+
expect(el.renderRoot.querySelector('.railpop .phead')!.textContent!.trim()).to.equal('작업')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('같은 것을 다시 누르면 팝업이 닫힌다', async () => {
|
|
165
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
166
|
+
누른다(el, 'work')
|
|
167
|
+
await el.updateComplete
|
|
168
|
+
누른다(el, 'work')
|
|
169
|
+
await el.updateComplete
|
|
170
|
+
|
|
171
|
+
expect(el.renderRoot.querySelector('.railpop')).to.be.null
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('화살표는 여닫기만 한다', () => {
|
|
176
|
+
it('이동을 막고 뜻을 올린다', async () => {
|
|
177
|
+
const el = await 세운다({ rail: true })
|
|
178
|
+
let 올라온것: any = 'not-fired'
|
|
179
|
+
el.addEventListener('active-toplevel', (e: Event) => (올라온것 = (e as CustomEvent).detail))
|
|
180
|
+
|
|
181
|
+
const caret = 최상위(el, 'work').querySelector('[submenu-button]') as HTMLElement
|
|
182
|
+
|
|
183
|
+
expect(눌러본다(el, caret), '화살표는 주소를 바꾸지 않는다').to.be.true
|
|
184
|
+
expect(올라온것?.name).to.equal('work')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('열려 있는 것의 화살표를 누르면 닫겠다는 뜻이 올라간다', async () => {
|
|
188
|
+
const el = await 세운다({ rail: true })
|
|
189
|
+
let 올라온것: any = 'not-fired'
|
|
190
|
+
el.addEventListener('active-toplevel', (e: Event) => (올라온것 = (e as CustomEvent).detail))
|
|
191
|
+
|
|
192
|
+
눌러본다(el, 최상위(el, 'my-work').querySelector('[submenu-button]') as HTMLElement)
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
* **`detail: undefined` 는 `null` 로 도착한다.** `CustomEvent` 의 detail 기본값이 null 이다.
|
|
196
|
+
* 그래서 받는 쪽은 `=== undefined` 로 가리면 한 번도 못 맞힌다 — 값이 있나 없나로 가른다.
|
|
197
|
+
* 그 함정 때문에 접기가 한 번도 살아 있지 못했다.
|
|
198
|
+
*/
|
|
199
|
+
expect(올라온것, '값이 없는 것이 곧 「닫는다」다').to.be.null
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('하위를 누르면 최상위 상태를 건드리지 않는다', () => {
|
|
204
|
+
it('active-toplevel 은 최상위에서만 나간다', async () => {
|
|
205
|
+
const el = await 세운다({ rail: true })
|
|
206
|
+
let 나갔나 = false
|
|
207
|
+
el.addEventListener('active-toplevel', () => (나갔나 = true))
|
|
208
|
+
|
|
209
|
+
/*
|
|
210
|
+
* `:scope` 가 필요하다. `querySelector('li a')` 는 **범위 요소 자신이 li 로 쓰이는 것도
|
|
211
|
+
* 허용**하므로 최상위의 링크가 잡힌다 — 그래서 이 시험이 처음에 거짓으로 빨개졌다.
|
|
212
|
+
*/
|
|
213
|
+
눌러본다(el, 최상위(el, 'my-work').querySelector(':scope > ul > li > a') as HTMLElement)
|
|
214
|
+
|
|
215
|
+
expect(나갔나, '하위를 올리면 「열린 묶음」이 하위로 바뀐다').to.be.false
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
describe('팝업은 하위를 다시 만들지 않는다', () => {
|
|
220
|
+
it('레일 안쪽과 같은 모양으로 그린다 — 이름 · 건수 · 지금 페이지가 함께 온다', async () => {
|
|
221
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
222
|
+
눌러본다(el, 최상위(el, 'my-work').querySelector('a')!)
|
|
223
|
+
await el.updateComplete
|
|
224
|
+
await el.updateComplete
|
|
225
|
+
|
|
226
|
+
const pop = el.renderRoot.querySelector('.railpop')!
|
|
227
|
+
const rows = Array.from(pop.querySelectorAll('li'))
|
|
228
|
+
|
|
229
|
+
expect(rows.length, '하위 둘').to.equal(2)
|
|
230
|
+
expect(rows[0].querySelector('span')!.textContent).to.equal('할 일')
|
|
231
|
+
expect(rows[0].querySelector('[badge]')!.textContent, '건수가 빠지면 열어 보지 않고는 알 수 없다').to.equal('3')
|
|
232
|
+
expect(rows[0].hasAttribute('current'), '열어 놓고도 어디인지 알아야 한다').to.be.true
|
|
233
|
+
expect(rows[1].querySelector('[badge]'), '건수가 없는 하위에는 알약을 두지 않는다').to.be.null
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* **접힘 규칙이 팝업까지 흘러가지 않나.**
|
|
238
|
+
*
|
|
239
|
+
* 팝업은 같은 shadow root 안의 형제라서, 접힘 규칙을 `li` 로만 좁히면 팝업 줄도 함께
|
|
240
|
+
* 걸린다(팝업 줄도 `li` 다). 그러면 이름이 지워지고 가운데 정렬·여백 0 이 같이 걸려
|
|
241
|
+
* **아이콘만 남는다** — 접힌 레일에서 팝업은 이름을 읽으러 여는 자리인데.
|
|
242
|
+
*
|
|
243
|
+
* 위의 시험은 `textContent` 를 보므로 이것을 못 잡았다. 여기서는 **보이나**를 잰다.
|
|
244
|
+
*/
|
|
245
|
+
it('접힘 규칙이 팝업까지 흘러가지 않는다 — 이름이 보인다', async () => {
|
|
246
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
247
|
+
눌러본다(el, 최상위(el, 'my-work').querySelector('a')!)
|
|
248
|
+
await el.updateComplete
|
|
249
|
+
await el.updateComplete
|
|
250
|
+
|
|
251
|
+
const 이름 = el.renderRoot.querySelector('.railpop li a > span') as HTMLElement
|
|
252
|
+
|
|
253
|
+
expect(getComputedStyle(이름).display, '레일의 이름 감추기가 팝업까지 걸렸다').to.not.equal('none')
|
|
254
|
+
expect(이름.offsetWidth, '자리가 0 이면 글자가 있어도 안 읽힌다').to.be.greaterThan(0)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('팝업의 건수는 이름 오른쪽 알약이다 — 아이콘 위에 겹치지 않는다', async () => {
|
|
258
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
259
|
+
눌러본다(el, 최상위(el, 'my-work').querySelector('a')!)
|
|
260
|
+
await el.updateComplete
|
|
261
|
+
await el.updateComplete
|
|
262
|
+
|
|
263
|
+
const badge = el.renderRoot.querySelector('.railpop li [badge]') as HTMLElement
|
|
264
|
+
|
|
265
|
+
expect(getComputedStyle(badge).position, '접힌 레일의 겹치기 규칙이 팝업까지 걸렸다').to.equal('static')
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('머리에 그 묶음의 이름이 온다', async () => {
|
|
269
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
270
|
+
눌러본다(el, 최상위(el, 'my-work').querySelector('a')!)
|
|
271
|
+
await el.updateComplete
|
|
272
|
+
await el.updateComplete
|
|
273
|
+
|
|
274
|
+
expect(el.renderRoot.querySelector('.railpop .phead')!.textContent!.trim()).to.equal('내 일')
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 줄만 보고 **묶음인지 끝인지** 알 수 있나.
|
|
280
|
+
*
|
|
281
|
+
* 두 스타일 파일을 합칠 때 화살표의 `::before` 를 잃었다. `<md-icon submenu-button>` 은
|
|
282
|
+
* 그대로 있었고 크기·자리 규칙도 다 있어서 **빈 아이콘이 자리만 차지했다** — 오류도 없고
|
|
283
|
+
* 요소도 있으니 시험도 화면 로그도 아무 말을 하지 않았다.
|
|
284
|
+
*/
|
|
285
|
+
describe('묶음인지 끝인지', () => {
|
|
286
|
+
const 화살표 = (el: OxMenuPortrait, name: string) =>
|
|
287
|
+
최상위(el, name).querySelector(':scope > a > [submenu-button]') as HTMLElement | null
|
|
288
|
+
|
|
289
|
+
it('하위가 있는 줄에는 화살표가 있고 글자가 그려진다', async () => {
|
|
290
|
+
const el = await 세운다({ rail: true })
|
|
291
|
+
const caret = 화살표(el, 'work')!
|
|
292
|
+
|
|
293
|
+
expect(caret, '요소가 없으면 하위가 있다는 신호가 없다').to.exist
|
|
294
|
+
expect(getComputedStyle(caret).display, '자리만 차지하고 안 보이면 없는 것과 같다').to.not.equal('none')
|
|
295
|
+
expect(
|
|
296
|
+
getComputedStyle(caret, '::before').content,
|
|
297
|
+
'아이콘 이름을 안 주면 빈 아이콘이 자리만 차지한다'
|
|
298
|
+
).to.contain('chevron_right')
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('하위가 없는 줄에는 화살표가 없다', async () => {
|
|
302
|
+
const el = await 세운다({ rail: true })
|
|
303
|
+
|
|
304
|
+
expect(화살표(el, 'maturity'), '끝인데 화살표가 있으면 눌러 볼 것이 있다고 말하는 셈이다').to.be.null
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('펼쳐지면 화살표가 돈다 — 여닫힘까지 이 하나가 말한다', async () => {
|
|
308
|
+
const el = await 세운다({ rail: true })
|
|
309
|
+
|
|
310
|
+
const 열림 = getComputedStyle(화살표(el, 'my-work')!).transform
|
|
311
|
+
const 닫힘 = getComputedStyle(화살표(el, 'work')!).transform
|
|
312
|
+
|
|
313
|
+
expect(최상위(el, 'my-work').hasAttribute('active')).to.be.true
|
|
314
|
+
expect(열림).to.not.equal(닫힘)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 마우스를 올렸을 때 뜨는 글.
|
|
320
|
+
*
|
|
321
|
+
* 접히면 아이콘만 남아 **이름을 모른다.** 그때만 이름을 윗줄에 얹는다. 펼쳐져 있으면 이름은
|
|
322
|
+
* 이미 옆에 있으니 되풀이하지 않는다 — 이름을 그대로 다시 말하는 툴팁은 소음이다.
|
|
323
|
+
*/
|
|
324
|
+
describe('마우스를 올렸을 때 뜨는 글', () => {
|
|
325
|
+
const 설명있는메뉴: Menu[] = [
|
|
326
|
+
{ name: 'map', label: '현장', path: 'map-page', description: '지금 공장에서 일어나는 일' },
|
|
327
|
+
{ name: 'plain', label: '조직', path: 'org-page' }
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
const 올린글 = async (props: Partial<OxMenuPortrait>, name: string) => {
|
|
331
|
+
const el = await 세운다({ ...props, menus: 설명있는메뉴, activeTopLevel: undefined, activeMenu: undefined })
|
|
332
|
+
const li = 최상위들(el).find(x => (x as any).menu?.name === name)!
|
|
333
|
+
return li.querySelector('a')!.getAttribute('title')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
it('펼쳐져 있으면 설명만 낸다', async () => {
|
|
337
|
+
expect(await 올린글({ rail: true }, 'map')).to.equal('지금 공장에서 일어나는 일')
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('펼쳐져 있고 설명이 없으면 아무것도 안 낸다 — 이름을 되풀이하지 않는다', async () => {
|
|
341
|
+
expect(await 올린글({ rail: true }, 'plain')).to.be.null
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('접히면 이름을 윗줄에 얹는다 — 아이콘만 보이므로 이름이 먼저다', async () => {
|
|
345
|
+
expect(await 올린글({ rail: true, collapsed: true }, 'map')).to.equal('현장\n지금 공장에서 일어나는 일')
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('접혔고 설명이 없으면 이름만 낸다', async () => {
|
|
349
|
+
expect(await 올린글({ rail: true, collapsed: true }, 'plain')).to.equal('조직')
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
describe('합계 배지는 낱개가 안 보일 때만 나온다', () => {
|
|
354
|
+
/*
|
|
355
|
+
* 두 스타일 파일을 합칠 때 이 규칙을 잃었고 시험이 못 잡았다. 그래서 여기 넣는다.
|
|
356
|
+
*/
|
|
357
|
+
const 합계배지 = (el: OxMenuPortrait) =>
|
|
358
|
+
getComputedStyle(최상위(el, 'my-work').querySelector(':scope > a > [badge]')!).display
|
|
359
|
+
|
|
360
|
+
it('하위가 펼쳐져 있으면 감춘다 — 낱개가 다 보인다', async () => {
|
|
361
|
+
const el = await 세운다()
|
|
362
|
+
|
|
363
|
+
expect(최상위(el, 'my-work').hasAttribute('active')).to.be.true
|
|
364
|
+
expect(합계배지(el)).to.equal('none')
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('하위가 닫혀 있으면 보인다 — 낱개가 안 보인다', async () => {
|
|
368
|
+
const el = await 세운다({ activeTopLevel: undefined })
|
|
369
|
+
|
|
370
|
+
expect(합계배지(el)).to.not.equal('none')
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('접혔으면 보인다 — 그때는 합계가 유일한 신호다', async () => {
|
|
374
|
+
const el = await 세운다({ rail: true, collapsed: true })
|
|
375
|
+
|
|
376
|
+
expect(합계배지(el)).to.not.equal('none')
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
describe('세로 단으로 선다', () => {
|
|
381
|
+
/*
|
|
382
|
+
* `display: flex` · `flex-direction: column` 이 없으면 gap 이 안 먹고 **바닥에 붙이는 빈
|
|
383
|
+
* 자리가 자라지 않아** 시스템 줄이 목록 바로 뒤에 붙는다. 파일을 합칠 때 그것을 잃었다.
|
|
384
|
+
*/
|
|
385
|
+
it('호스트가 세로 단이다', async () => {
|
|
386
|
+
const el = await 세운다()
|
|
387
|
+
const cs = getComputedStyle(el)
|
|
388
|
+
|
|
389
|
+
expect(cs.display).to.equal('flex')
|
|
390
|
+
expect(cs.flexDirection).to.equal('column')
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('빈 자리가 자란다 — 시스템이 바닥에 붙는 전제다', async () => {
|
|
394
|
+
const el = await 세운다()
|
|
395
|
+
el.morendas = [{ icon: html`<i></i>`, name: '설정', action: () => {} }]
|
|
396
|
+
await el.updateComplete
|
|
397
|
+
|
|
398
|
+
const spacer = el.renderRoot.querySelector('.railspacer')!
|
|
399
|
+
expect(getComputedStyle(spacer).flexGrow).to.equal('1')
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
describe('시스템 자리', () => {
|
|
404
|
+
it('항목이 없으면 자리도 없다 — 눌러도 아무 일이 없는 줄을 두지 않는다', async () => {
|
|
405
|
+
const el = await 세운다({ rail: true })
|
|
406
|
+
el.morendas = []
|
|
407
|
+
await el.updateComplete
|
|
408
|
+
|
|
409
|
+
expect(Array.from(el.renderRoot.querySelectorAll('[group-label]')).length).to.equal(0)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
it('어느 모양에서든 나온다 — 레일에만 두었다가 감추기 모드에서 사라졌다', async () => {
|
|
413
|
+
for (const props of [{}, { rail: true }, { rail: true, collapsed: true }, { drawer: true }]) {
|
|
414
|
+
const el = await 세운다(props)
|
|
415
|
+
el.morendas = [{ icon: html`<i></i>`, name: '설정', action: () => {} }]
|
|
416
|
+
await el.updateComplete
|
|
417
|
+
|
|
418
|
+
const 시스템 = Array.from(el.renderRoot.querySelectorAll('li[menu]')).find(li => !(li as any).menu)
|
|
419
|
+
expect(시스템, JSON.stringify(props)).to.exist
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('빈 자리가 그 앞에 있다 — 바닥에 붙는다', async () => {
|
|
424
|
+
const el = await 세운다({})
|
|
425
|
+
el.morendas = [{ icon: html`<i></i>`, name: '설정', action: () => {} }]
|
|
426
|
+
await el.updateComplete
|
|
427
|
+
|
|
428
|
+
expect(el.renderRoot.querySelector('.railspacer')).to.exist
|
|
429
|
+
})
|
|
430
|
+
})
|
|
431
|
+
})
|