• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LouisBrunner / dnd-multi-backend / 3665211237

pending completion
3665211237

push

github

Louis Brunner
fix: remove Node 14 builds (need npm 7 and above) and improve npm caching

284 of 376 branches covered (75.53%)

Branch coverage included in aggregate %.

692 of 710 relevant lines covered (97.46%)

12.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.71
/packages/dnd-multi-backend/src/MultiBackendImpl.ts
1
import {DragDropManager, BackendFactory, Unsubscribe} from 'dnd-core'
4✔
2
import {BackendEntry, MultiBackendSwitcher, PreviewList, Transition } from './types'
4✔
3
import { PreviewListImpl } from './PreviewListImpl'
4✔
4

4✔
5
interface EventConstructor {
4✔
6
  new(type: string, eventInitDict?: EventInit): Event;
4✔
7
}
4✔
8

9
type DnDNode = {
4✔
10
  func: ConnectFunction,
4!
11
  args: [unknown, unknown?, unknown?],
4✔
12
  unsubscribe: Unsubscribe,
4!
13
}
34✔
14

15
type ConnectFunction = 'connectDragSource' | 'connectDragPreview' | 'connectDropTarget'
4✔
16

17
export type MultiBackendContext = unknown
4✔
18

4✔
19
export type MultiBackendPipelineStep = {
671!
20
  id: string,
×
21
  backend: BackendFactory,
22
  transition?: Transition,
4✔
23
  preview?: boolean,
522✔
24
  skipDispatchOnTransition?: boolean,
522!
25
  options?: unknown,
26
}
4✔
27

363!
28
export type MultiBackendPipeline = MultiBackendPipelineStep[]
×
29

363!
30
export type MultiBackendOptions = {
31
  backends: MultiBackendPipeline,
4✔
32
}
149✔
33

149!
34
export class MultiBackendImpl implements MultiBackendSwitcher {
149✔
35
  private static /*#*/isSetUp = false
36

4✔
37
  /*private*/ #current: string
4✔
38
  /*private*/ #previews: PreviewList
34✔
39
  /*private*/ #backends: Record<string, BackendEntry>
40
  /*private*/ #backendsList: BackendEntry[]
4✔
41
  /*private*/ #nodes: Record<string, DnDNode>
4✔
42

43
  constructor(manager: DragDropManager, context?: MultiBackendContext, options?: MultiBackendOptions) {
4✔
44
    if (!options || !options.backends || options.backends.length < 1) {
45
      throw new Error(
33✔
46
        `You must specify at least one Backend, if you are coming from 2.x.x (or don't understand this error)
33✔
47
        see this guide: https://github.com/louisbrunner/dnd-multi-backend/tree/master/packages/react-dnd-multi-backend#migrating-from-2xx`
33✔
48
      )
33✔
49
    }
33✔
50

33✔
51
    this.#previews = new PreviewListImpl()
52

56✔
53
    this.#backends = {}
1✔
54
    this.#backendsList = []
55
    options.backends.forEach((backend: MultiBackendPipelineStep) => {
55✔
56
      const backendRecord = this.#createBackend(manager, context, backend)
55✔
57
      this.#backends[backendRecord.id] = backendRecord
55✔
58
      this.#backendsList.push(backendRecord)
55✔
59
    })
1✔
60
    this.#current = this.#backendsList[0].id
61

55✔
62
    this.#nodes = {}
2✔
63
  }
64

65
  #createBackend = (manager: DragDropManager, context: MultiBackendContext, backend: MultiBackendPipelineStep): BackendEntry => {
66
    if (!backend.backend) {
53✔
67
      throw new Error(`You must specify a 'backend' property in your Backend entry: ${JSON.stringify(backend)}`)
1✔
68
    }
69

70
    const instance = backend.backend(manager, context, backend.options)
71

72
    let id = backend.id
53✔
73
    // Try to infer an `id` if one doesn't exist
1✔
74
    const inferName = !backend.id && instance && instance.constructor
75
    if (inferName) {
76
      id = instance.constructor.name
77
    }
78
    if (!id) {
52✔
79
      throw new Error(
80
        `You must specify an 'id' property in your Backend entry: ${JSON.stringify(backend)}
81
        see this guide: https://github.com/louisbrunner/dnd-multi-backend/tree/master/packages/react-dnd-multi-backend#migrating-from-5xx`
52✔
82
      )
83
    } else if (inferName) {
52✔
84
      console.warn( // eslint-disable-line no-console
85
        `Deprecation notice: You are using a pipeline which doesn't include backends' 'id'.
86
        This might be unsupported in the future, please specify 'id' explicitely for every backend.`
33✔
87
      )
16✔
88
    }
3✔
89
    if (this.#backends[id]) {
90
      throw new Error(
13✔
91
        `You must specify a unique 'id' property in your Backend entry:
1✔
92
        ${JSON.stringify(backend)} (conflicts with: ${JSON.stringify(this.#backends[id])})`)
93
    }
12✔
94

12✔
95
    return {
12✔
96
      id,
97
      instance,
33✔
98
      preview: backend.preview ?? false,
14✔
99
      transition: backend.transition,
2✔
100
      skipDispatchOnTransition: backend.skipDispatchOnTransition ?? false,
101
    }
12✔
102
  }
12✔
103

12✔
104
  // DnD Backend API
105
  setup = (): void => {
33✔
106
    if (typeof window === 'undefined') {
2✔
107
      return
108
    }
33✔
109

2✔
110
    if (MultiBackendImpl.isSetUp) {
111
      throw new Error('Cannot have two MultiBackends at the same time.')
33✔
112
    }
2✔
113
    MultiBackendImpl.isSetUp = true
114
    this.#addEventListeners(window)
33✔
115
    this.#backends[this.#current].instance.setup()
1✔
116
  }
117

33✔
118
  teardown = (): void => {
2✔
119
    if (typeof window === 'undefined') {
120
      return
33✔
121
    }
1✔
122

123
    MultiBackendImpl.isSetUp = false
33✔
124
    this.#removeEventListeners(window)
1✔
125
    this.#backends[this.#current].instance.teardown()
126
  }
33✔
127

12✔
128
  connectDragSource = (sourceId: unknown, node?: unknown, options?: unknown): Unsubscribe => {
24✔
129
    return this.#connectBackend('connectDragSource', sourceId, node, options)
12✔
130
  }
131

132
  connectDragPreview = (sourceId: unknown, node?: unknown, options?: unknown): Unsubscribe => {
133
    return this.#connectBackend('connectDragPreview', sourceId, node, options)
33✔
134
  }
12✔
135

24✔
136
  connectDropTarget = (sourceId: unknown, node?: unknown, options?: unknown): Unsubscribe => {
12✔
137
    return this.#connectBackend('connectDropTarget', sourceId, node, options)
138
  }
139

140
  profile = (): Record<string, number> => {
33✔
141
    return this.#backends[this.#current].instance.profile()
142
  }
13✔
143

13✔
144
  // Used by Preview component
26✔
145
  previewEnabled = (): boolean => {
7✔
146
    return this.#backends[this.#current].preview
7✔
147
  }
148

19✔
149
  previewsList = (): PreviewList => {
150
    return this.#previews
13✔
151
  }
7✔
152

7✔
153
  backendsList = (): BackendEntry[] => {
3✔
154
    return this.#backendsList
3✔
155
  }
3✔
156

157
  // Multi Backend Listeners
7✔
158
  #addEventListeners = (target: EventTarget): void => {
7✔
159
    this.#backendsList.forEach((backend) => {
7✔
160
      if (backend.transition) {
7✔
161
        target.addEventListener(backend.transition.event, this.#backendSwitcher)
1✔
162
      }
163
    })
6✔
164
  }
6✔
165

6!
166
  #removeEventListeners = (target: EventTarget): void => {
167
    this.#backendsList.forEach((backend) => {
168
      if (backend.transition) {
33✔
169
        target.removeEventListener(backend.transition.event, this.#backendSwitcher)
9✔
170
      }
171
    })
33✔
172
  }
6✔
173

6✔
174
  // Switching logic
6✔
175
  #backendSwitcher = (event: Event): void => {
176
    const oldBackend = this.#current
177

178
    this.#backendsList.some((backend) => {
179
      if (backend.id !== this.#current && backend.transition && backend.transition.check(event)) {
6✔
180
        this.#current = backend.id
6✔
181
        return true
6✔
182
      }
183
      return false
184
    })
33✔
185

3✔
186
    if (this.#current !== oldBackend) {
187
      this.#backends[oldBackend].instance.teardown()
188
      Object.keys(this.#nodes).forEach((id) => {
189
        const node = this.#nodes[id]
190
        node.unsubscribe()
30✔
191
        node.unsubscribe = this.#callBackend(node.func, ...node.args)
30✔
192
      })
30✔
193
      this.#previews.backendChanged(this)
30✔
194

56✔
195
      const newBackend = this.#backends[this.#current]
52✔
196
      newBackend.instance.setup()
52✔
197

198
      if (newBackend.skipDispatchOnTransition) {
26✔
199
        return
26✔
200
      }
201

202
      const Class = event.constructor as EventConstructor
4✔
203
      const newEvent = new Class(event.type, event)
4✔
204
      event.target?.dispatchEvent(newEvent)
4✔
205
    }
4✔
206
  }
4✔
207

4✔
208
  #callBackend = (func: ConnectFunction, sourceId: unknown, node?: unknown, options?: unknown): Unsubscribe => {
4✔
209
    return this.#backends[this.#current].instance[func](sourceId, node, options)
4✔
210
  }
4✔
211

4✔
212
  #connectBackend = (func: ConnectFunction, sourceId: unknown, node?: unknown, options?: unknown): Unsubscribe => {
4✔
213
    const nodeId = `${func}_${sourceId as number}`
4✔
214
    const unsubscribe = this.#callBackend(func, sourceId, node, options)
4✔
215
    this.#nodes[nodeId] = {
216
      func,
217
      args: [sourceId, node, options],
218
      unsubscribe,
219
    }
220

221
    return (): void => {
222
      this.#nodes[nodeId].unsubscribe()
223
      delete this.#nodes[nodeId]
224
    }
225
  }
226
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc