Skip to content

Commit e503d99

Browse files
committed
reintroduce some utility functions
A few utilities were extracted into the `tealight` package, since they proved reusable and relevant to front-end development... However, rather than creating an opinionated utility library, the `tealight` package has been pruned to focus on DOM queries; alas, the rejected utilities make their back into the source (for now).
1 parent a19b80d commit e503d99

6 files changed

Lines changed: 204 additions & 0 deletions

File tree

src/utils/deep-assign.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import isObject from './is-object'
2+
import each from './each'
3+
4+
export default function deepAssign (target, ...sources) {
5+
if (isObject(target)) {
6+
each(sources, source => {
7+
each(source, (data, key) => {
8+
if (isObject(data)) {
9+
if (!target[key] || !isObject(target[key])) {
10+
target[key] = {}
11+
}
12+
deepAssign(target[key], data)
13+
} else {
14+
target[key] = data
15+
}
16+
})
17+
})
18+
return target
19+
} else {
20+
throw new TypeError('Target must be an object literal.')
21+
}
22+
}

src/utils/each.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import isObject from './is-object'
2+
3+
export default function each (collection, callback) {
4+
if (isObject(collection)) {
5+
const keys = Object.keys(collection)
6+
return keys.forEach(key => callback(collection[key], key, collection))
7+
}
8+
if (collection instanceof Array) {
9+
return collection.forEach((item, i) => callback(item, i, collection))
10+
}
11+
throw new TypeError('Expected either an array or object literal.')
12+
}

src/utils/is-object.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function isObject (x) {
2+
return (
3+
x !== null &&
4+
x instanceof Object &&
5+
(x.constructor === Object || Object.prototype.toString.call(x) === '[object Object]')
6+
)
7+
}

test/utils/deep-assign.spec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import deepAssign from '../../src/utils/deep-assign'
2+
3+
describe('Utilities', () => {
4+
describe('deepAssign()', () => {
5+
it('should assign source values to target object', () => {
6+
const target = { foo: 'bar', bun: 'baz' }
7+
const source = { foo: 'bonk!', bif: 'baff' }
8+
const goal = { foo: 'bonk!', bun: 'baz', bif: 'baff' }
9+
deepAssign(target, source)
10+
expect(target).to.deep.equal(goal)
11+
})
12+
13+
it('should assign nested source values to target object', () => {
14+
// each property tests a
15+
// different execution path
16+
const target = {
17+
foo: 'initial',
18+
bar: 'initial',
19+
kel: { pow: 'pop' },
20+
zad: null,
21+
}
22+
const source = {
23+
foo: 'bonk!',
24+
bar: { baz: 'baff' },
25+
kel: { pow: 'lol' },
26+
zad: { min: 'max' },
27+
}
28+
const goal = {
29+
foo: 'bonk!',
30+
bar: { baz: 'baff' },
31+
kel: { pow: 'lol' },
32+
zad: { min: 'max' },
33+
}
34+
deepAssign(target, source)
35+
expect(target).to.deep.equal(goal)
36+
})
37+
38+
it('should accept multiple sources', () => {
39+
const target = { foo: 'bar', bun: 'baz' }
40+
const source1 = { foo: 'bonk!', bif: 'baff' }
41+
const source2 = { foo: 'pow!' }
42+
const goal = { foo: 'pow!', bun: 'baz', bif: 'baff' }
43+
deepAssign(target, source1, source2)
44+
expect(target).to.deep.equal(goal)
45+
})
46+
47+
it('should throw a type error when not passed an object literal', () => {
48+
let caught
49+
try {
50+
deepAssign(null, null)
51+
} catch (error) {
52+
caught = error
53+
}
54+
expect(caught).to.exist
55+
expect(caught).to.be.an.instanceof(TypeError)
56+
})
57+
})
58+
})

test/utils/each.spec.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import each from '../../src/utils/each'
2+
3+
describe('Utilities', () => {
4+
describe('each()', () => {
5+
function Fixture () {
6+
this.foo = 'bar'
7+
this.baz = 'bun'
8+
}
9+
10+
describe('if passed an object literal...', () => {
11+
it('should invoke callback for each property', () => {
12+
const fixture = new Fixture()
13+
const spy = sinon.spy()
14+
each(fixture, spy)
15+
expect(spy).to.have.been.calledTwice
16+
})
17+
18+
it('should ignore properties on the prototype chain', () => {
19+
Fixture.prototype.biff = 'baff'
20+
const fixture = new Fixture()
21+
const spy = sinon.spy()
22+
each(fixture, spy)
23+
expect(spy).to.have.been.calledTwice
24+
})
25+
26+
it('should pass the value, key and collection to the callback', () => {
27+
const fixture = new Fixture()
28+
let _value, _key, _collection
29+
each(fixture, (value, key, collection) => {
30+
_value = value
31+
_key = key
32+
_collection = collection
33+
})
34+
expect(_value).to.equal('bun')
35+
expect(_key).to.equal('baz')
36+
expect(_collection).to.deep.equal(fixture)
37+
})
38+
})
39+
40+
describe('if passed an array...', () => {
41+
const fixture = ['apple', 'orange', 'banana']
42+
43+
it('should invoke callback for each value', () => {
44+
const spy = sinon.spy()
45+
each(fixture, spy)
46+
expect(spy).to.have.been.calledThrice
47+
})
48+
49+
it('should pass the value, index and collection to the callback', () => {
50+
let _value, _index, _collection
51+
each(fixture, (value, index, collection) => {
52+
_value = value
53+
_index = index
54+
_collection = collection
55+
})
56+
expect(_value).to.equal('banana')
57+
expect(_index).to.equal(2)
58+
expect(_collection).to.deep.equal(fixture)
59+
})
60+
})
61+
62+
describe('else', () => {
63+
it('should throw a type error when passed an invalid collection', () => {
64+
let caught
65+
try {
66+
each(null, () => {})
67+
} catch (error) {
68+
caught = error
69+
}
70+
expect(caught).to.exist
71+
expect(caught).to.be.an.instanceof(TypeError)
72+
})
73+
})
74+
})
75+
})

test/utils/is-object.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import isObject from '../../src/utils/is-object'
2+
3+
describe('Utilities', () => {
4+
describe('isObject()', () => {
5+
it('should return true when passed an object literal', () => {
6+
const result = isObject({})
7+
expect(result).to.be.true
8+
})
9+
10+
it('should return false when passed a function', () => {
11+
const result = isObject(() => {})
12+
expect(result).to.be.false
13+
})
14+
15+
it('should return false when passed an array', () => {
16+
const result = isObject([])
17+
expect(result).to.be.false
18+
})
19+
20+
it('should return false when passed null', () => {
21+
const result = isObject(null)
22+
expect(result).to.be.false
23+
})
24+
25+
it('should return false when passed undefined', () => {
26+
const result = isObject(undefined)
27+
expect(result).to.be.false
28+
})
29+
})
30+
})

0 commit comments

Comments
 (0)