diff --git a/src/HandlerRegistry.js b/src/HandlerRegistry.js index afbf4d4..1cb132a 100644 --- a/src/HandlerRegistry.js +++ b/src/HandlerRegistry.js @@ -1,6 +1,8 @@ import invariant from 'invariant'; import isArray from 'lodash/isArray'; import getNextUniqueId from './utils/getNextUniqueId'; +import isValidSourceType from './utils/isValidSourceType'; +import isValidTargetType from './utils/isValidTargetType'; import { addSource, addTarget, removeSource, removeTarget } from './actions/registry'; import asap from 'asap'; @@ -21,20 +23,6 @@ function validateTargetContract(target) { invariant(typeof target.drop === 'function', 'Expected beginDrag to be a function.'); } -function validateType(type, allowArray) { - if (allowArray && isArray(type)) { - type.forEach(t => validateType(t, false)); - return; - } - - invariant( - typeof type === 'string' || typeof type === 'symbol', - allowArray ? - 'Type can only be a string, a symbol, or an array of either.' : - 'Type can only be a string or a symbol.' - ); -} - function getNextHandlerId(role) { const id = getNextUniqueId().toString(); switch (role) { @@ -70,7 +58,10 @@ export default class HandlerRegistry { } addSource(type, source) { - validateType(type); + invariant( + isValidSourceType(type), + 'Source type can only be a string, a symbol, a boolean, or an array thereof.' + ); validateSourceContract(source); const sourceId = this.addHandler(HandlerRoles.SOURCE, type, source); @@ -79,7 +70,10 @@ export default class HandlerRegistry { } addTarget(type, target) { - validateType(type, true); + invariant( + isValidTargetType(type), + 'Target type can only be a string or a symbol.' + ); validateTargetContract(target); const targetId = this.addHandler(HandlerRoles.TARGET, type, target); diff --git a/src/index.js b/src/index.js index 2efb2bf..2b27307 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,6 @@ export { default as DragDropManager } from './DragDropManager'; export { default as DragSource } from './DragSource'; export { default as DropTarget } from './DropTarget'; -export { default as createTestBackend } from './backends/createTestBackend'; \ No newline at end of file +export { default as createTestBackend } from './backends/createTestBackend'; +export { default as isValidTargetType } from './utils/isValidTargetType'; +export { default as isValidSourceType } from './utils/isValidSourceType'; diff --git a/src/utils/isValidSourceType.js b/src/utils/isValidSourceType.js new file mode 100644 index 0000000..adcbc66 --- /dev/null +++ b/src/utils/isValidSourceType.js @@ -0,0 +1,5 @@ +export default function isValidSourceType(sourceType) { + const type = typeof sourceType; + return type === 'string' || type === 'symbol'; +} + diff --git a/src/utils/isValidTargetType.js b/src/utils/isValidTargetType.js new file mode 100644 index 0000000..a6e1f34 --- /dev/null +++ b/src/utils/isValidTargetType.js @@ -0,0 +1,18 @@ +import isArray from 'lodash/isArray'; + +export default function isValidTargetType(targetType) { + if (isArray(targetType)) { + for (let n = 0, len = targetType.length; n < len; n++) { + const type = typeof targetType[n]; + if (type !== 'boolean' && type !== 'string' && type !== 'symbol') { + return false; + } + } + + return true; + } else { + const type = typeof targetType; + return type === 'boolean' || type === 'string' || type === 'symbol'; + } +} + diff --git a/src/utils/matchesType.js b/src/utils/matchesType.js index 78d9230..38f5589 100644 --- a/src/utils/matchesType.js +++ b/src/utils/matchesType.js @@ -2,8 +2,8 @@ import isArray from 'lodash/isArray'; export default function matchesType(targetType, draggedItemType) { if (isArray(targetType)) { - return targetType.some(t => t === draggedItemType); + return targetType.some(t => t === true || t === draggedItemType); } else { - return targetType === draggedItemType; + return targetType === true || targetType === draggedItemType; } } diff --git a/test/API.spec.js b/test/API.spec.js new file mode 100644 index 0000000..bc8f6db --- /dev/null +++ b/test/API.spec.js @@ -0,0 +1,16 @@ +import expect from 'expect.js'; +import { isValidSourceType } from '../src'; +import { isValidTargetType } from '../src'; + +describe('API', () => { + describe('utils', (done) => { + it('exposes isValidSourceType', () => { + expect(isValidSourceType).to.be.a("function"); + }); + + it('exposes isValidTargetType', () => { + expect(isValidTargetType).to.be.a("function"); + }); + }); +}); + diff --git a/test/DragDropManager.spec.js b/test/DragDropManager.spec.js index 80c3c0f..f9b2d21 100644 --- a/test/DragDropManager.spec.js +++ b/test/DragDropManager.spec.js @@ -94,6 +94,21 @@ describe('DragDropManager', () => { expect(() => registry.addTarget([Symbol(), Symbol()], target)).to.not.throwError(); }); + // The target type true is a wildcard and matches all target types. The + // target type false matches no target types. + // + // Using true or false as a source type doesn't make sense and isn't + // supported. + it('accepts boolean as type for DropTargets, not for DragSources', () => { + const source = new NormalSource(); + const target = new NormalTarget(); + + expect(() => registry.addSource(true, source)).to.throwError(); + expect(() => registry.addTarget(true, target)).to.not.throwError(); + expect(() => registry.addTarget(false, target)).to.not.throwError(); + expect(() => registry.addTarget([true, true, false], target)).to.not.throwError(); + }); + it('throws on invalid type', () => { const source = new NormalSource(); const target = new NormalTarget(); @@ -358,13 +373,16 @@ describe('DragDropManager', () => { it('ignores drop() if target has a different type', () => { const source = new NormalSource(); const sourceId = registry.addSource(Types.FOO, source); - const target = new NormalTarget(); - const targetId = registry.addTarget(Types.BAR, target); + const targetA = new NormalTarget(); + const targetAId = registry.addTarget(Types.BAR, targetA); + const targetB = new NormalTarget(); + const targetBId = registry.addTarget(Types.NIL, targetB); backend.simulateBeginDrag([sourceId]); - backend.simulateHover([targetId]); + backend.simulateHover([targetAId, targetBId]); backend.simulateDrop(); - expect(target.didCallDrop).to.equal(false); + expect(targetA.didCallDrop).to.equal(false); + expect(targetB.didCallDrop).to.equal(false); }); it('throws in drop() if it is called outside a drag operation', () => { @@ -422,16 +440,20 @@ describe('DragDropManager', () => { const targetAId = registry.addTarget(Types.FOO, targetA); const targetB = new NormalTarget({ number: 16 }); const targetBId = registry.addTarget(Types.BAR, targetB); - const targetC = new NormalTarget({ number: 42 }); - const targetCId = registry.addTarget(Types.FOO, targetC); + const targetC = new NormalTarget({ number: 29 }); + const targetCId = registry.addTarget(Types.NIL, targetC); + const targetD = new NormalTarget({ number: 42 }); + const targetDId = registry.addTarget(Types.FOO, targetD); backend.simulateBeginDrag([sourceId]); - backend.simulateHover([targetAId, targetBId, targetCId]); + backend.simulateHover([targetAId, targetBId, targetCId, targetDId]); backend.simulateDrop(); backend.simulateEndDrag(); expect(targetA.didCallDrop).to.equal(true); expect(targetB.didCallDrop).to.equal(false); - expect(targetC.didCallDrop).to.equal(true); + console.log("C dropped?", targetC.didCallDrop); + expect(targetC.didCallDrop).to.equal(false); + expect(targetD.didCallDrop).to.equal(true); expect(source.recordedDropResult).to.eql({ number: 42 }); }); @@ -587,13 +609,22 @@ describe('DragDropManager', () => { const targetCId = registry.addTarget(Types.FOO, targetC); const targetD = new NormalTarget(); const targetDId = registry.addTarget([Types.BAZ, Types.FOO], targetD); + const targetE = new NormalTarget(); + const targetEId = registry.addTarget(Types.WILDCARD, targetE); + const targetF = new NormalTarget(); + const targetFId = registry.addTarget(Types.NIL, targetF); + const targetG = new NormalTarget(); + const targetGId = registry.addTarget([Types.WILDCARD, Types.NIL], targetG); backend.simulateBeginDrag([sourceId]); - backend.simulateHover([targetAId, targetBId, targetCId, targetDId]); + backend.simulateHover([targetAId, targetBId, targetCId, targetDId, targetEId, targetFId, targetGId]); expect(targetA.didCallHover).to.equal(true); expect(targetB.didCallHover).to.equal(false); expect(targetC.didCallHover).to.equal(true); expect(targetD.didCallHover).to.equal(true); + expect(targetE.didCallHover).to.equal(true); + expect(targetF.didCallHover).to.equal(false); + expect(targetG.didCallHover).to.equal(true); }); it('includes non-droppable targets when dispatching hover', () => { @@ -603,11 +634,14 @@ describe('DragDropManager', () => { const targetAId = registry.addTarget(Types.FOO, targetA); const targetB = new TargetWithNoDropResult(); const targetBId = registry.addTarget(Types.FOO, targetB); + const targetC = new TargetWithNoDropResult(); + const targetCId = registry.addTarget(Types.WILDCARD, targetC); backend.simulateBeginDrag([sourceId]); - backend.simulateHover([targetAId, targetBId]); + backend.simulateHover([targetAId, targetBId, targetCId]); expect(targetA.didCallHover).to.equal(true); expect(targetB.didCallHover).to.equal(true); + expect(targetC.didCallHover).to.equal(true); }); it('throws in hover() if it contains the same target twice', () => { diff --git a/test/types.js b/test/types.js index a7e4e42..970a8c3 100644 --- a/test/types.js +++ b/test/types.js @@ -1,3 +1,5 @@ export const FOO = 'FOO'; export const BAR = 'BAR'; export const BAZ = 'BAZ'; +export const WILDCARD = true; +export const NIL = false;