Skip to content

Commit

Permalink
Start detangling imitiate as prefactoring
Browse files Browse the repository at this point in the history
Also we stopped printing '#' as shorthand for 'prototype'. It was
complicating the code, and on further reflection it makes the messages
harder for anyone who's not "in the know" with respect to '#' being a
symbol for "instance method" (as opposed to '.' for plain function)
  • Loading branch information
searls committed Jun 23, 2017
1 parent 9aded7b commit dc2a4d5
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 44 deletions.
2 changes: 1 addition & 1 deletion regression/src/constructor-test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe 'td.constructor', ->

# Things print OK
Then -> @fakeConstructor.toString() == '[test double for "Thing"]'
Then -> @fakeConstructor.prototype.foo.toString() == '[test double for "Thing#foo"]'
Then -> @fakeConstructor.prototype.foo.toString() == '[test double for "Thing.prototype.foo"]'
Then -> @fakeConstructor.bar.toString() == '[test double for "Thing.bar"]'

# Non-enumerables are covered
Expand Down
8 changes: 4 additions & 4 deletions regression/src/object-test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ describe 'td.object', ->

if global.Proxy?
describe 'creating a proxy object (ES2015; only supported in FF + Edge atm)', ->
Given -> @testDouble = td.object('Thing')
Given -> @testDouble = td.object('thing')
Given -> @testDouble.magic('sauce')
When -> td.when(@testDouble.whateverYouWant()).thenReturn('YESS')
Then -> td.verify(@testDouble.magic('sauce'))
And -> @testDouble.whateverYouWant() == 'YESS'
And -> @testDouble.toString() == '[test double object for "Thing"]'
And -> @testDouble.foo.toString() == '[test double for "Thing#foo"]'
And -> @testDouble.toString() == '[test double object for "thing"]'
And -> @testDouble.foo.toString() == '[test double for "thing.foo"]'

context 'with custom excludeMethods definitions', ->
Given -> @testDouble = td.object('Stuff', excludeMethods: ['then', 'fun'])
Expand All @@ -51,7 +51,7 @@ describe 'td.object', ->
context 'unnamed double', ->
Given -> @testDouble = td.object()
Then -> @testDouble.toString() == '[test double object]'
Then -> @testDouble.lol.toString() == '[test double for "#lol"]'
Then -> @testDouble.lol.toString() == '[test double for ".lol"]'
else
describe 'getting an error message', ->
When -> try
Expand Down
2 changes: 1 addition & 1 deletion regression/src/verify-test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe '.verify', ->
Given -> @SomeType::biz = "not a function!"
Given -> @testDoubleObj = td.constructor(@SomeType)
When -> @result = (shouldThrow => td.verify(@testDoubleObj.prototype.baz()))
Then -> expect(@result).to.contain("verification on test double `Foo#baz`.")
Then -> expect(@result).to.contain("verification on test double `Foo.prototype.baz`.")
Then -> @testDoubleObj.prototype.biz == "not a function!"

context 'with a test double *as an arg* to another', ->
Expand Down
66 changes: 31 additions & 35 deletions src/imitate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,49 @@ export default function imitate (original, names, encounteredObjects = new Map()
if (encounteredObjects.has(original)) return encounteredObjects.get(original)
if (_.isArguments(original)) original = _.toArray(original)

// Initialize name array
if (names == null) {
if (_.isArray(original)) {
names = []
} else if (_.isFunction(original)) {
names = [original.name]
} else {
let name = original.name || _.invoke(original, 'toString') || ''
if (name === ({}).toString()) {
name = ''
}
names = [name]
}
}

let target
if (_.isArray(original)) {
if (names == null) names = []
target = _.map(original, (item, index) => {
return imitate(item, names.concat(`[${index}]`), encounteredObjects)
})
target = []
} else if (_.isFunction(original)) {
if (names == null) names = [original.name]
target = tdFunction(_.compact(names).join('') || '(anonymous function)')
} else {
if (names == null) names = [nameFromObject(original)]
target = _.clone(original)
}
encounteredObjects.set(original, target)
if (!blacklistedValueType(original)) {
copyProps(target, gatherProps(original), (name, value) => {
if (name === 'prototype' && _.isFunction(original) && Object.hasOwnProperty.call(original, 'prototype')) {
const extendedPrototype = imitate(Object.create(value), concatName(names, name), encounteredObjects)
extendedPrototype.constructor = target
return extendedPrototype
} else {
return imitate(value, concatName(names, name), encounteredObjects)
}
})
if (!blacklistedValueType(target)) {
if (_.isArray(target)) {
_.each(original, (item, index) =>
target.push(imitate(item, names.concat(`[${index}]`), encounteredObjects))
)
} else {
copyProps(target, gatherProps(original), (name, originalValue) => {
const targetValue = imitate(originalValue, names.concat('.', name), encounteredObjects)
if (name === 'prototype' && _.isFunction(original) && Object.hasOwnProperty.call(original, 'prototype')) {
targetValue.__proto__ = originalValue // eslint-disable-line
targetValue.constructor = target
}
return targetValue
})
}
}
return target
}

const concatName = (names, name) => {
if (name === 'prototype') {
return names.concat('#')
} else if (_.last(names) === '#') {
return names.concat(name)
} else {
return names.concat('.', name)
}
}

const blacklistedValueType = (thing) =>
_.compact([
Boolean,
Expand All @@ -57,12 +62,3 @@ const blacklistedValueType = (thing) =>
String,
global.Symbol
]).some(type => thing instanceof type)

const nameFromObject = (obj) => {
const name = obj.name || _.invoke(obj, 'toString') || ''
if (name === ({}).toString()) {
return ''
} else {
return name
}
}
2 changes: 1 addition & 1 deletion src/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var createTestDoubleViaProxy = (name, config) => {
return new Proxy(obj, {
get (target, propKey, receiver) {
if (!obj.hasOwnProperty(propKey) && !_.includes(config.excludeMethods, propKey)) {
obj[propKey] = tdFunction(`${nameOf(name)}#${propKey}`)
obj[propKey] = tdFunction(`${nameOf(name)}.${propKey}`)
}
return obj[propKey]
}
Expand Down
4 changes: 2 additions & 2 deletions test/safe/imitate/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ module.exports = {
const result = subject(Thing)

assert.equal(explain(result).name, 'Thing')
assert.equal(explain(result.prototype.doStuff).name, 'Thing#doStuff')
assert.equal(explain(result.prototype.doStuff.bar.baz).name, 'Thing#doStuff.bar.baz')
assert.equal(explain(result.prototype.doStuff).name, 'Thing.prototype.doStuff')
assert.equal(explain(result.prototype.doStuff.bar.baz).name, 'Thing.prototype.doStuff.bar.baz')
},
'name array things okay': () => {
const original = {
Expand Down

0 comments on commit dc2a4d5

Please sign in to comment.