Skip to content

Commit a36a89c

Browse files
committed
Symbols must be stringified explicitly
Apparently (nodejs/node#927 (comment)), Symbols cannot be implicitly coerced to string. Template stringification leverages implicit coercion, so we must stringify propKey before sending it to the template. Options for explicitly stringifying: - `String(propKey)` - `String.prototype.toString(propKey)` Differences: ``` > String(undefined) 'undefined' > String.prototype.toString(undefined) '' ``` In this case, `propKey` represents the key for the stubbed function. If the key is `undefined`, it would be implicitly coerced to `"undefined"` as the property key. So `String()` is the explicit converstion we want. Four instances of this issue, each where tdFunction is invoked with a dynamic name.
1 parent ec416a0 commit a36a89c

File tree

6 files changed

+43
-4
lines changed

6 files changed

+43
-4
lines changed

regression/src/constructor-test.coffee

+11
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ describe 'td.constructor', ->
7272
And -> @fakeInstance.foo.toString() == '[test double for "#foo"]'
7373

7474

75+
if (global.Symbol)
76+
describe 'edge case: being given a Symbol as function name', ->
77+
Given -> @symbolFoo = Symbol('foo')
78+
Given -> @fakeConstructor = td.constructor([@symbolFoo])
79+
Given -> @fakeInstance = new @fakeConstructor('biz')
80+
Then -> @fakeConstructor.prototype[@symbolFoo] == @fakeInstance[@symbolFoo]
81+
And -> td.verify(new @fakeConstructor('biz'))
82+
And -> td.explain(@fakeInstance[@symbolFoo]).isTestDouble == true
83+
And -> @fakeConstructor.toString() == '[test double for "(unnamed constructor)"]'
84+
And -> @fakeInstance.toString() == '[test double instance of constructor]'
85+
And -> @fakeInstance[@symbolFoo].toString() == '[test double for "#Symbol(foo)"]'
7586

7687
describe 'edge case: being given a function without prototypal methods', ->
7788
Given -> @boringFunc = ->

regression/src/object-test.coffee

+14
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ describe 'td.object', ->
2525
And -> @testDouble.toString() == '[test double object]'
2626
And -> @testDouble.bam.toString() == '[test double for ".bam"]'
2727

28+
if (global.Symbol)
29+
context 'making a test double based on a Symbol', ->
30+
Given -> @symbolFoo = Symbol('foo')
31+
Given -> @testDouble = td.object([@symbolFoo])
32+
When -> td.when(@testDouble[@symbolFoo]()).thenReturn('zing!')
33+
Then -> @testDouble[@symbolFoo]() == 'zing!'
34+
And -> @testDouble.toString() == '[test double object]'
35+
And -> @testDouble[@symbolFoo].toString() == '[test double for ".Symbol(foo)"]'
36+
2837
describe 'passing a function to td.object erroneously (1.x)', ->
2938
When -> try td.object(->) catch e then @result = e
3039
Then -> expect(@result.message).to.contain(
@@ -52,6 +61,11 @@ describe 'td.object', ->
5261
Given -> @testDouble = td.object()
5362
Then -> @testDouble.toString() == '[test double object]'
5463
Then -> @testDouble.lol.toString() == '[test double for ".lol"]'
64+
65+
if (global.Symbol)
66+
context 'with Symbol propKey', ->
67+
And -> @testDouble[Symbol('foo')].toString() == '[test double for "thing.Symbol(foo)"]'
68+
5569
else
5670
describe 'getting an error message', ->
5771
When -> try

src/constructor.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ var fakeConstructorFromNames = (funcNames) => {
1313
'[test double instance of constructor]'
1414

1515
_.each(funcNames, (funcName) => {
16-
fakeConstructor.prototype[funcName] = tdFunction(`#${funcName}`)
16+
fakeConstructor.prototype[funcName] = tdFunction(`#${String(funcName)}`)
1717
})
1818
})
1919
}

src/imitate/create-imitation.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default (original, names) => {
1111
return original
1212
} else {
1313
// TODO: this will become src/function/create and include parent reference instead of name joining here
14-
return tdFunction(names.join('') || '(anonymous function)')
14+
return tdFunction(names.map(String).join('') || '(anonymous function)')
1515
}
1616
} else {
1717
return _.clone(original)

src/object.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var fakeObject = (nameOrType, config) => {
2626

2727
var createTestDoublesForFunctionNames = (names) =>
2828
_.transform(names, (acc, funcName) => {
29-
acc[funcName] = tdFunction(`.${funcName}`)
29+
acc[funcName] = tdFunction(`.${String(funcName)}`)
3030
})
3131

3232
var createTestDoubleViaProxy = (name, config) => {
@@ -35,7 +35,7 @@ var createTestDoubleViaProxy = (name, config) => {
3535
return new Proxy(obj, {
3636
get (target, propKey, receiver) {
3737
if (!obj.hasOwnProperty(propKey) && !_.includes(config.excludeMethods, propKey)) {
38-
obj[propKey] = tdFunction(`${nameOf(name)}.${propKey}`)
38+
obj[propKey] = tdFunction(`${nameOf(name)}.${String(propKey)}`)
3939
}
4040
return obj[propKey]
4141
}

test/unit/imitate/create-imitation.test.js

+14
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ module.exports = {
3030

3131
assert.deepEqual(result, 'fake thing')
3232
},
33+
'a function with symbols' () {
34+
if (!global.Symbol) return
35+
36+
const someFunc = () => {}
37+
const symFoo = Symbol('foo')
38+
const symBar = Symbol('bar')
39+
td.when(tdFunction('Symbol(foo)Symbol(bar)'))
40+
.thenReturn('fake thing')
41+
td.when(isGenerator(someFunc)).thenReturn(false)
42+
43+
const result = subject(someFunc, [symFoo, symBar])
44+
45+
assert.deepEqual(result, 'fake thing')
46+
},
3347
'other instances': () => {
3448
const original = {a: 'b'}
3549

0 commit comments

Comments
 (0)