@talelk/newui-organisms 0.0.0-stage → 1.0.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/LICENSE +202 -0
- package/README.md +12 -2
- package/package.json +38 -3
- package/src/exports.test.ts +27 -0
- package/src/index.ts +1 -0
- package/src/organisms/AppSidebar/AppSidebar.test.tsx +100 -0
- package/src/organisms/AppSidebar/AppSidebar.tsx +214 -0
- package/src/organisms/AppSidebar/index.ts +1 -0
- package/src/organisms/AuthCard/AuthCard.a11y.test.tsx +36 -0
- package/src/organisms/AuthCard/AuthCard.test.tsx +56 -0
- package/src/organisms/AuthCard/AuthCard.tsx +80 -0
- package/src/organisms/AuthCard/index.ts +1 -0
- package/src/organisms/CommandPalette/CommandPalette.test.tsx +196 -0
- package/src/organisms/CommandPalette/CommandPalette.tsx +457 -0
- package/src/organisms/CommandPalette/index.ts +1 -0
- package/src/organisms/DataTable/DataTable.test.tsx +89 -0
- package/src/organisms/DataTable/DataTable.tsx +334 -0
- package/src/organisms/DataTable/index.ts +1 -0
- package/src/organisms/DetailHeader/DetailHeader.test.tsx +55 -0
- package/src/organisms/DetailHeader/DetailHeader.tsx +87 -0
- package/src/organisms/DetailHeader/index.ts +1 -0
- package/src/organisms/FilterBar/FilterBar.test.tsx +151 -0
- package/src/organisms/FilterBar/FilterBar.tsx +232 -0
- package/src/organisms/FilterBar/index.ts +1 -0
- package/src/organisms/KanbanBoard/KanbanBoard.test.tsx +255 -0
- package/src/organisms/KanbanBoard/KanbanBoard.tsx +550 -0
- package/src/organisms/KanbanBoard/drag.test.ts +448 -0
- package/src/organisms/KanbanBoard/drag.ts +407 -0
- package/src/organisms/KanbanBoard/index.ts +17 -0
- package/src/organisms/MobileNav/MobileNav.browser.test.tsx +39 -0
- package/src/organisms/MobileNav/MobileNav.test.tsx +108 -0
- package/src/organisms/MobileNav/MobileNav.tsx +120 -0
- package/src/organisms/MobileNav/index.ts +6 -0
- package/src/organisms/PageToolbar/PageToolbar.a11y.test.tsx +35 -0
- package/src/organisms/PageToolbar/PageToolbar.test.tsx +42 -0
- package/src/organisms/PageToolbar/PageToolbar.tsx +77 -0
- package/src/organisms/PageToolbar/index.ts +1 -0
- package/src/organisms/SettingsDialog/SettingsDialog.a11y.test.tsx +58 -0
- package/src/organisms/SettingsDialog/SettingsDialog.browser.test.tsx +43 -0
- package/src/organisms/SettingsDialog/SettingsDialog.ssr.test.tsx +27 -0
- package/src/organisms/SettingsDialog/SettingsDialog.test.tsx +202 -0
- package/src/organisms/SettingsDialog/SettingsDialog.tsx +539 -0
- package/src/organisms/SettingsDialog/index.ts +20 -0
- package/src/organisms/Sidebar/Sidebar.browser.test.tsx +52 -0
- package/src/organisms/Sidebar/Sidebar.test.tsx +308 -0
- package/src/organisms/Sidebar/Sidebar.tsx +725 -0
- package/src/organisms/Sidebar/__screenshots__/Sidebar.browser.test.tsx/Sidebar--browser--real-CSS--reserves-its-declared-width-1.png +0 -0
- package/src/organisms/Sidebar/index.ts +22 -0
- package/src/organisms/StatGrid/StatGrid.a11y.test.tsx +40 -0
- package/src/organisms/StatGrid/StatGrid.test.tsx +83 -0
- package/src/organisms/StatGrid/StatGrid.tsx +124 -0
- package/src/organisms/StatGrid/index.ts +1 -0
- package/src/organisms/VirtualList/VirtualList.test.tsx +80 -0
- package/src/organisms/VirtualList/VirtualList.tsx +79 -0
- package/src/organisms/VirtualList/index.ts +1 -0
- package/src/organisms/index.ts +13 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { createRoot } from 'solid-js'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import {
|
|
4
|
+
createKanbanDrag,
|
|
5
|
+
type KanbanDrag,
|
|
6
|
+
type KanbanDragOptions,
|
|
7
|
+
type KanbanSlot,
|
|
8
|
+
} from './drag'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The gesture is tested against fabricated geometry, because that is the only
|
|
12
|
+
* way to test it at all: jsdom reports every box as a zero rect, so "is the
|
|
13
|
+
* pointer past this card's midpoint" would be unanswerable. Every element the
|
|
14
|
+
* engine touches is injected, so the boxes are just numbers here — the
|
|
15
|
+
* thresholds, the hit-test arithmetic, the auto-scroll edges and the drop
|
|
16
|
+
* payload are asserted directly rather than through a drag the harness cannot
|
|
17
|
+
* perform.
|
|
18
|
+
*
|
|
19
|
+
* The frame loop is stubbed rather than timed: `frame()` runs exactly one tick,
|
|
20
|
+
* so a test says how many frames it means instead of waiting for one.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface Box {
|
|
24
|
+
left: number
|
|
25
|
+
top: number
|
|
26
|
+
width: number
|
|
27
|
+
height: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function boxed(box: Box, cards: FakeCard[] = []): HTMLElement {
|
|
31
|
+
const node = document.createElement('div')
|
|
32
|
+
node.getBoundingClientRect = () => rect(box)
|
|
33
|
+
for (const card of cards) {
|
|
34
|
+
const inner = document.createElement('div')
|
|
35
|
+
inner.setAttribute('data-kanban-card', '')
|
|
36
|
+
inner.setAttribute('data-kanban-id', card.id)
|
|
37
|
+
if (card.dragging) inner.setAttribute('data-dragging', '')
|
|
38
|
+
inner.getBoundingClientRect = () =>
|
|
39
|
+
rect({
|
|
40
|
+
left: box.left,
|
|
41
|
+
top: card.top,
|
|
42
|
+
width: box.width,
|
|
43
|
+
height: card.height,
|
|
44
|
+
})
|
|
45
|
+
node.append(inner)
|
|
46
|
+
}
|
|
47
|
+
return node
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface FakeCard {
|
|
51
|
+
id: string
|
|
52
|
+
top: number
|
|
53
|
+
height: number
|
|
54
|
+
dragging?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rect(box: Box): DOMRect {
|
|
58
|
+
return {
|
|
59
|
+
x: box.left,
|
|
60
|
+
y: box.top,
|
|
61
|
+
left: box.left,
|
|
62
|
+
top: box.top,
|
|
63
|
+
right: box.left + box.width,
|
|
64
|
+
bottom: box.top + box.height,
|
|
65
|
+
width: box.width,
|
|
66
|
+
height: box.height,
|
|
67
|
+
toJSON: () => ({}),
|
|
68
|
+
} as DOMRect
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** jsdom's `scrollLeft`/`scrollTop` never move without layout; make them fields. */
|
|
72
|
+
function scrollable(node: HTMLElement): HTMLElement {
|
|
73
|
+
Object.defineProperty(node, 'scrollLeft', { value: 0, writable: true })
|
|
74
|
+
Object.defineProperty(node, 'scrollTop', { value: 0, writable: true })
|
|
75
|
+
return node
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A `pointerdown`-shaped event: jsdom has no PointerEvent constructor. */
|
|
79
|
+
function pointer(
|
|
80
|
+
type: 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel',
|
|
81
|
+
options: { x: number; y: number; pointerType?: string; button?: number },
|
|
82
|
+
// `window` too: the engine listens for the move and the release there.
|
|
83
|
+
target?: EventTarget,
|
|
84
|
+
): PointerEvent {
|
|
85
|
+
const event = new MouseEvent(type, {
|
|
86
|
+
clientX: options.x,
|
|
87
|
+
clientY: options.y,
|
|
88
|
+
button: options.button ?? 0,
|
|
89
|
+
bubbles: true,
|
|
90
|
+
}) as unknown as PointerEvent
|
|
91
|
+
Object.defineProperty(event, 'pointerType', {
|
|
92
|
+
value: options.pointerType ?? 'mouse',
|
|
93
|
+
})
|
|
94
|
+
target?.dispatchEvent(event)
|
|
95
|
+
return event
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface Harness extends KanbanDragOptions<{ id: string }> {
|
|
99
|
+
drag: KanbanDrag<{ id: string }>
|
|
100
|
+
previews: { itemId: string; slot: KanbanSlot }[]
|
|
101
|
+
drops: { item: { id: string }; from: KanbanSlot; to: KanbanSlot }[]
|
|
102
|
+
cancels: number
|
|
103
|
+
board: HTMLElement
|
|
104
|
+
column: HTMLElement
|
|
105
|
+
card: HTMLElement
|
|
106
|
+
frame: () => void
|
|
107
|
+
press: (at?: { x: number; y: number }, pointerType?: string) => void
|
|
108
|
+
move: (at: { x: number; y: number }) => void
|
|
109
|
+
up: (at?: { x: number; y: number }) => void
|
|
110
|
+
dispose: () => void
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function harness(overrides: Partial<KanbanDragOptions<{ id: string }>> = {}) {
|
|
114
|
+
let tick: FrameRequestCallback | undefined
|
|
115
|
+
vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => {
|
|
116
|
+
tick = callback
|
|
117
|
+
return 1
|
|
118
|
+
})
|
|
119
|
+
vi.stubGlobal('cancelAnimationFrame', () => {
|
|
120
|
+
tick = undefined
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const board = scrollable(boxed({ left: 0, top: 0, width: 1200, height: 600 }))
|
|
124
|
+
// The board holds the slot the ghost lands on: the element the component
|
|
125
|
+
// marks while a card is in flight.
|
|
126
|
+
board.append(
|
|
127
|
+
boxed({ left: 16, top: 300, width: 288, height: 100 }, [
|
|
128
|
+
{ id: 'slot', top: 300, height: 100, dragging: true },
|
|
129
|
+
]),
|
|
130
|
+
)
|
|
131
|
+
const column = scrollable(
|
|
132
|
+
boxed({ left: 16, top: 60, width: 288, height: 520 }, [
|
|
133
|
+
{ id: 'a', top: 80, height: 100 },
|
|
134
|
+
{ id: 'b', top: 190, height: 100 },
|
|
135
|
+
{ id: 'c', top: 300, height: 100 },
|
|
136
|
+
]),
|
|
137
|
+
)
|
|
138
|
+
const won = scrollable(boxed({ left: 316, top: 60, width: 288, height: 520 }))
|
|
139
|
+
|
|
140
|
+
/** The grabbed card's own box: the ghost and the slot both match it. */
|
|
141
|
+
const card = boxed({ left: 16, top: 80, width: 288, height: 100 })
|
|
142
|
+
|
|
143
|
+
const test = {
|
|
144
|
+
board,
|
|
145
|
+
column,
|
|
146
|
+
card,
|
|
147
|
+
previews: [] as Harness['previews'],
|
|
148
|
+
drops: [] as Harness['drops'],
|
|
149
|
+
cancels: 0,
|
|
150
|
+
} as Harness
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Where the item sits, as the board would answer it: the preview *is* the
|
|
154
|
+
* placement once reported, which is what makes the engine's "has it actually
|
|
155
|
+
* changed?" check meaningful instead of always true.
|
|
156
|
+
*/
|
|
157
|
+
let current: KanbanSlot = { column: 'open', index: 0 }
|
|
158
|
+
|
|
159
|
+
test.frame = () => {
|
|
160
|
+
const current = tick
|
|
161
|
+
tick = undefined
|
|
162
|
+
current?.(0)
|
|
163
|
+
}
|
|
164
|
+
test.press = (at = { x: 100, y: 100 }, pointerType = 'mouse') => {
|
|
165
|
+
const event = pointer('pointerdown', { ...at, pointerType }, test.card)
|
|
166
|
+
test.drag.start(event, { id: 'a' }, test.card)
|
|
167
|
+
}
|
|
168
|
+
test.move = (at) => pointer('pointermove', { ...at }, window)
|
|
169
|
+
test.up = (at = { x: 0, y: 0 }) => pointer('pointerup', { ...at }, window)
|
|
170
|
+
|
|
171
|
+
createRoot((dispose) => {
|
|
172
|
+
test.dispose = dispose
|
|
173
|
+
test.drag = createKanbanDrag<{ id: string }>({
|
|
174
|
+
container: () => board,
|
|
175
|
+
columnScroller: (id) => (id === 'open' ? column : won),
|
|
176
|
+
columnIds: () => ['open', 'won'],
|
|
177
|
+
slotOf: () => current,
|
|
178
|
+
onPreview: (itemId, slot) => {
|
|
179
|
+
current = slot
|
|
180
|
+
test.previews.push({ itemId, slot })
|
|
181
|
+
},
|
|
182
|
+
onDrop: (item, from, to) => test.drops.push({ item, from, to }),
|
|
183
|
+
onCancel: () => {
|
|
184
|
+
test.cancels += 1
|
|
185
|
+
},
|
|
186
|
+
...overrides,
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return test
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Press, then drift past the mouse threshold: the card is in the air. */
|
|
194
|
+
function lift(test: Harness, at = { x: 100, y: 100 }) {
|
|
195
|
+
test.press(at)
|
|
196
|
+
test.move({ x: at.x + 6, y: at.y })
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Run the settle timer the release started. */
|
|
200
|
+
const drop = () => vi.advanceTimersByTime(1000)
|
|
201
|
+
|
|
202
|
+
describe('createKanbanDrag', () => {
|
|
203
|
+
beforeEach(() => {
|
|
204
|
+
vi.useFakeTimers()
|
|
205
|
+
})
|
|
206
|
+
afterEach(() => {
|
|
207
|
+
vi.useRealTimers()
|
|
208
|
+
vi.unstubAllGlobals()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('does not lift a card on a press that never moves', () => {
|
|
212
|
+
const test = harness()
|
|
213
|
+
test.press()
|
|
214
|
+
test.up({ x: 100, y: 100 })
|
|
215
|
+
|
|
216
|
+
expect(test.drag.state()).toBeUndefined()
|
|
217
|
+
expect(test.drops).toEqual([])
|
|
218
|
+
expect(test.cancels).toBe(0)
|
|
219
|
+
test.dispose()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('lifts once the mouse travels past the threshold', () => {
|
|
223
|
+
const test = harness()
|
|
224
|
+
test.press()
|
|
225
|
+
test.move({ x: 103, y: 100 })
|
|
226
|
+
expect(test.drag.state()).toBeUndefined()
|
|
227
|
+
|
|
228
|
+
test.move({ x: 106, y: 100 })
|
|
229
|
+
expect(test.drag.state()?.itemId).toBe('a')
|
|
230
|
+
// The ghost starts exactly on the card it replaced.
|
|
231
|
+
expect(test.drag.state()?.x).toBe(16)
|
|
232
|
+
expect(test.drag.state()?.y).toBe(80)
|
|
233
|
+
expect(test.drag.state()?.width).toBe(288)
|
|
234
|
+
expect(test.drag.state()?.height).toBe(100)
|
|
235
|
+
test.dispose()
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('reads a drifting finger as a scroll, and a still one as a grab', () => {
|
|
239
|
+
const drifting = harness()
|
|
240
|
+
drifting.press({ x: 100, y: 100 }, 'touch')
|
|
241
|
+
drifting.move({ x: 130, y: 100 })
|
|
242
|
+
vi.advanceTimersByTime(400)
|
|
243
|
+
expect(drifting.drag.state()).toBeUndefined()
|
|
244
|
+
drifting.dispose()
|
|
245
|
+
|
|
246
|
+
const holding = harness()
|
|
247
|
+
holding.press({ x: 100, y: 100 }, 'touch')
|
|
248
|
+
vi.advanceTimersByTime(400)
|
|
249
|
+
expect(holding.drag.state()?.itemId).toBe('a')
|
|
250
|
+
holding.dispose()
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('knows which item is in the air', () => {
|
|
254
|
+
const test = harness()
|
|
255
|
+
lift(test)
|
|
256
|
+
expect(test.drag.isDragging('a')).toBe(true)
|
|
257
|
+
expect(test.drag.isDragging('b')).toBe(false)
|
|
258
|
+
test.dispose()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('leaves a press that starts on a control inside the card alone', () => {
|
|
262
|
+
const test = harness()
|
|
263
|
+
const button = document.createElement('button')
|
|
264
|
+
const event = pointer('pointerdown', { x: 100, y: 100 }, button)
|
|
265
|
+
test.drag.start(event, { id: 'a' }, test.card)
|
|
266
|
+
test.move({ x: 160, y: 100 })
|
|
267
|
+
|
|
268
|
+
expect(test.drag.state()).toBeUndefined()
|
|
269
|
+
expect(test.cancels).toBe(0)
|
|
270
|
+
test.dispose()
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('counts the insertion index over the other cards only', () => {
|
|
274
|
+
const test = harness()
|
|
275
|
+
lift(test)
|
|
276
|
+
|
|
277
|
+
// The item is already at the top, so a frame over that spot changes nothing
|
|
278
|
+
// and the board is not asked to re-render.
|
|
279
|
+
test.frame()
|
|
280
|
+
expect(test.previews).toEqual([])
|
|
281
|
+
|
|
282
|
+
// Past the first card's midpoint (80 + 100 / 2 = 130), and nothing else's.
|
|
283
|
+
test.move({ x: 100, y: 140 })
|
|
284
|
+
test.frame()
|
|
285
|
+
expect(test.previews.at(-1)?.slot).toEqual({ column: 'open', index: 1 })
|
|
286
|
+
|
|
287
|
+
// Past the second's (240), not the third's (350).
|
|
288
|
+
test.move({ x: 100, y: 250 })
|
|
289
|
+
test.frame()
|
|
290
|
+
expect(test.previews.at(-1)?.slot).toEqual({ column: 'open', index: 2 })
|
|
291
|
+
|
|
292
|
+
// And back above them all.
|
|
293
|
+
test.move({ x: 100, y: 70 })
|
|
294
|
+
test.frame()
|
|
295
|
+
expect(test.previews.at(-1)?.slot).toEqual({ column: 'open', index: 0 })
|
|
296
|
+
test.dispose()
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('reports a slot once, not once per frame', () => {
|
|
300
|
+
const test = harness()
|
|
301
|
+
lift(test)
|
|
302
|
+
test.move({ x: 100, y: 140 })
|
|
303
|
+
test.frame()
|
|
304
|
+
test.frame()
|
|
305
|
+
test.frame()
|
|
306
|
+
// The first frame crossed into a new slot; the next two did not.
|
|
307
|
+
expect(test.previews).toEqual([
|
|
308
|
+
{ itemId: 'a', slot: { column: 'open', index: 1 } },
|
|
309
|
+
])
|
|
310
|
+
test.dispose()
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('previews nothing while the pointer is in the gap between columns', () => {
|
|
314
|
+
const test = harness()
|
|
315
|
+
lift(test)
|
|
316
|
+
test.move({ x: 300, y: 100 })
|
|
317
|
+
test.frame()
|
|
318
|
+
|
|
319
|
+
// 300 is past the first column's right edge (304 would be inside it) and
|
|
320
|
+
// short of the second's left one (316): neither column is a target.
|
|
321
|
+
expect(test.previews).toEqual([])
|
|
322
|
+
test.dispose()
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('moves the ghost with the pointer, holding where it was grabbed', () => {
|
|
326
|
+
const test = harness()
|
|
327
|
+
lift(test)
|
|
328
|
+
// Grabbed 90px in from the card's left edge, 20px from its top.
|
|
329
|
+
test.frame()
|
|
330
|
+
expect(test.drag.state()?.x).toBe(16)
|
|
331
|
+
expect(test.drag.state()?.y).toBe(80)
|
|
332
|
+
|
|
333
|
+
test.move({ x: 400, y: 260 })
|
|
334
|
+
test.frame()
|
|
335
|
+
expect(test.drag.state()?.x).toBe(310)
|
|
336
|
+
expect(test.drag.state()?.y).toBe(240)
|
|
337
|
+
test.dispose()
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('auto-scrolls the board and the column at their edges', () => {
|
|
341
|
+
const board = scrollable(
|
|
342
|
+
boxed({ left: 0, top: 0, width: 600, height: 600 }),
|
|
343
|
+
)
|
|
344
|
+
board.append(
|
|
345
|
+
boxed({ left: 16, top: 300, width: 288, height: 100 }, [
|
|
346
|
+
{ id: 'slot', top: 300, height: 100, dragging: true },
|
|
347
|
+
]),
|
|
348
|
+
)
|
|
349
|
+
const column = scrollable(
|
|
350
|
+
boxed({ left: 16, top: 60, width: 288, height: 520 }, [
|
|
351
|
+
{ id: 'a', top: 80, height: 100 },
|
|
352
|
+
]),
|
|
353
|
+
)
|
|
354
|
+
const test = harness({
|
|
355
|
+
container: () => board,
|
|
356
|
+
columnScroller: () => column,
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
// Top-left: both scrollers drift back.
|
|
360
|
+
test.press({ x: 30, y: 100 })
|
|
361
|
+
test.move({ x: 36, y: 100 })
|
|
362
|
+
test.frame()
|
|
363
|
+
expect(board.scrollLeft).toBeLessThan(0)
|
|
364
|
+
expect(column.scrollTop).toBeLessThan(0)
|
|
365
|
+
|
|
366
|
+
// Bottom-right: both drift forward.
|
|
367
|
+
test.move({ x: 590, y: 560 })
|
|
368
|
+
test.frame()
|
|
369
|
+
expect(board.scrollLeft).toBeGreaterThan(0)
|
|
370
|
+
expect(column.scrollTop).toBeGreaterThan(0)
|
|
371
|
+
test.dispose()
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('reports the drop once, with where the gesture started', () => {
|
|
375
|
+
const test = harness()
|
|
376
|
+
test.press({ x: 100, y: 70 })
|
|
377
|
+
test.move({ x: 400, y: 300 })
|
|
378
|
+
test.frame()
|
|
379
|
+
test.up({ x: 400, y: 300 })
|
|
380
|
+
drop()
|
|
381
|
+
|
|
382
|
+
expect(test.drops).toHaveLength(1)
|
|
383
|
+
expect(test.drops[0]?.item).toEqual({ id: 'a' })
|
|
384
|
+
expect(test.drops[0]?.from).toEqual({ column: 'open', index: 0 })
|
|
385
|
+
expect(test.drops[0]?.to).toEqual({ column: 'won', index: 0 })
|
|
386
|
+
expect(test.drag.state()).toBeUndefined()
|
|
387
|
+
|
|
388
|
+
// The gesture is over: a second release is not a second move.
|
|
389
|
+
test.up({ x: 400, y: 300 })
|
|
390
|
+
expect(test.drops).toHaveLength(1)
|
|
391
|
+
test.dispose()
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('cancels when the card is put back where it started', () => {
|
|
395
|
+
const test = harness()
|
|
396
|
+
lift(test)
|
|
397
|
+
test.up({ x: 106, y: 100 })
|
|
398
|
+
drop()
|
|
399
|
+
|
|
400
|
+
expect(test.drops).toEqual([])
|
|
401
|
+
expect(test.cancels).toBe(1)
|
|
402
|
+
expect(test.drag.state()).toBeUndefined()
|
|
403
|
+
test.dispose()
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('cancels on Escape, and when the browser takes the gesture back', () => {
|
|
407
|
+
const escaped = harness()
|
|
408
|
+
lift(escaped)
|
|
409
|
+
escaped.drag.cancel()
|
|
410
|
+
expect(escaped.cancels).toBe(1)
|
|
411
|
+
expect(escaped.drag.state()).toBeUndefined()
|
|
412
|
+
escaped.dispose()
|
|
413
|
+
|
|
414
|
+
const cancelled = harness()
|
|
415
|
+
lift(cancelled)
|
|
416
|
+
pointer('pointercancel', { x: 106, y: 100 }, window)
|
|
417
|
+
expect(cancelled.cancels).toBe(1)
|
|
418
|
+
expect(cancelled.drag.state()).toBeUndefined()
|
|
419
|
+
cancelled.dispose()
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('takes every listener and timer back when it is disposed', () => {
|
|
423
|
+
const added = vi.spyOn(window, 'addEventListener')
|
|
424
|
+
const removed = vi.spyOn(window, 'removeEventListener')
|
|
425
|
+
const cleared = vi.spyOn(window, 'clearTimeout')
|
|
426
|
+
|
|
427
|
+
const test = harness()
|
|
428
|
+
test.press({ x: 100, y: 100 }, 'touch')
|
|
429
|
+
test.move({ x: 130, y: 100 })
|
|
430
|
+
test.dispose()
|
|
431
|
+
|
|
432
|
+
expect(removed.mock.calls.map((call) => call[0]).sort()).toEqual(
|
|
433
|
+
added.mock.calls.map((call) => call[0]).sort(),
|
|
434
|
+
)
|
|
435
|
+
expect(cleared).toHaveBeenCalled()
|
|
436
|
+
expect(test.drag.state()).toBeUndefined()
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('survives a press on an item the board no longer has', () => {
|
|
440
|
+
const test = harness({ slotOf: () => undefined })
|
|
441
|
+
lift(test)
|
|
442
|
+
|
|
443
|
+
expect(test.drag.state()).toBeUndefined()
|
|
444
|
+
test.up({ x: 106, y: 100 })
|
|
445
|
+
expect(test.cancels).toBe(0)
|
|
446
|
+
test.dispose()
|
|
447
|
+
})
|
|
448
|
+
})
|