Created
January 16, 2019 05:51
-
-
Save bsredbeard/0bab118ee916935092198b0e5b8b502e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Verifier(name, calls){ | |
if(!Array.isArray(calls)){ | |
throw new Error(`Expected ${name} to have been called, but no calls were found.`); | |
} | |
var scopeCalls = calls; | |
this.withArguments = (...args) => { | |
scopeCalls = calls.filter((call) => { | |
return call.length === args.length && | |
call.map((arg, idx) => arg === args[idx]) | |
.reduce((x,y) => x && y, true); | |
}); | |
if(scopeCalls.length < 1){ | |
throw new Error(`Expected ${name} to have been called with [${args.join(', ')}], but it wasn't.`) | |
} | |
return this; | |
}; | |
this.times = (numCalls) => { | |
if(numCalls !== scopeCalls.length) { | |
throw new Error(`Expected ${name} to have been called ${numCalls} times, but was called ${calls.length} times instead.`); | |
} | |
return this; | |
}; | |
this.and = () => { | |
scopeCalls = calls; | |
return this; | |
} | |
Object.defineProperties(this, { | |
calls: { | |
get() { return scopeCalls.slice(); }, | |
enumerable: true | |
}, | |
length: { | |
get() { return scopeCalls.length; }, | |
enumerable: true | |
} | |
}); | |
} | |
function MockLocation(url){ | |
const urlMatcher = /^([^:]+):\/+([^\/?#]+)(\/[^?#]*)?(\?[^#]+)?(#.*)?/; | |
const userHostPortMatcher = /^(?:([^@]+)@)?([^:]+):?(\d+)?$/; | |
const makeState = () => ({ | |
href: undefined, | |
protocol: undefined, | |
host: undefined, | |
hostname: undefined, | |
port: undefined, | |
pathname: undefined, | |
search: undefined, | |
hash: undefined, | |
userpassword: undefined | |
}); | |
var state = null; | |
const mocking = {}; | |
function record() { | |
const args = Array.prototype.slice.apply(arguments, []); | |
const name = args.shift(); | |
if(!mocking[name]){ | |
mocking[name] = []; | |
} | |
mocking[name].push(args); | |
} | |
Object.defineProperties(this, { | |
href: { | |
get() { | |
record('get_href') | |
return state.href; | |
}, | |
set(val) { | |
record('set_href', val); | |
const match = urlMatcher.exec(val); | |
if(match){ | |
state = makeState(); | |
state.href = val; | |
state.protocol = match[1]; | |
state.pathname = match[3] || '/'; | |
state.search = match[4] || '?'; | |
state.hash = match[5] || '#'; | |
const userHostPort = userHostPortMatcher.exec(match[2]); | |
if(userHostPort){ | |
state.userpassword = userHostPort[1]; | |
state.hostname = userHostPort[2]; | |
state.port = userHostPort[3] || '80'; | |
state.host = state.hostname + ':' + state.port; | |
} else { | |
state.host = match[2]; | |
state.hostname = match[2] | |
} | |
} else { | |
throw new Error('Invalid url: ' + url); | |
} | |
} | |
}, | |
protocol: { | |
get() { | |
record('get_protocol'); | |
return state.protocol; | |
}, | |
set() { throw new Error('location.protocol is a readonly property'); } | |
}, | |
host: { | |
get() { | |
record('get_host') | |
return state.host; | |
}, | |
set() { throw new Error('location.host is a readonly property'); } | |
}, | |
hostname: { | |
get() { | |
record('get_hostname'); | |
return state.hostname; | |
}, | |
set() { throw new Error('location.host is a readonly property'); } | |
}, | |
port: { | |
get() { | |
record('get_port'); | |
return state.port; | |
}, | |
set() { throw new Error('location.port is a readonly property'); } | |
}, | |
pathname: { | |
get() { | |
record('get_pathname'); | |
return state.pathname; | |
}, | |
set() { throw new Error('location.pathname is a readonly property'); } | |
}, | |
search: { | |
get() { | |
record('get_search'); | |
return state.search; | |
}, | |
set() { throw new Error('location.search is a readonly property'); } | |
}, | |
hash: { | |
get() { | |
record('get_hash'); | |
return state.hash; | |
}, | |
set() { throw new Error('location.hash is a readonly property'); } | |
}, | |
username: { | |
get() { | |
record('get_username'); | |
var m = /[^:]+/.exec(state.userpassword||''); | |
return m ? m[0] : undefined; | |
}, | |
set() { throw new Error('location.username is a readonly property'); } | |
}, | |
password: { | |
get() { | |
record('get_password'); | |
var m = /:(.+)/.exec(state.userpassword||''); | |
return m ? m[1] : undefined; | |
}, | |
set() { throw new Error('location.password is a readonly property'); } | |
}, | |
origin: { | |
get() { | |
record('get_origin'); | |
return `${state.protocol}://${state.host}`; | |
}, | |
set() { throw new Error('location.origin is a readonly property'); } | |
}, | |
assign: { | |
value: (newUrl) => { | |
record('call_assign', newUrl); | |
this.href = newUrl; | |
}, | |
writable: false | |
}, | |
replace: { | |
value: (newUrl) => { | |
record('call_replace', newUrl); | |
this.href = newUrl; | |
}, | |
writable: false | |
}, | |
reload: { | |
value: (force) => { | |
record('call_reload', force); | |
}, | |
writable: false | |
}, | |
toString: { | |
value: () => { | |
record('call_toString') | |
return state.href; | |
}, | |
writable: false | |
}, | |
verifyCall: { | |
value: (name) => new Verifier(`call_${name}`, mocking[`call_${name}`]), | |
writable: false, | |
enumerable: true | |
}, | |
verifyGet: { | |
value: (name) => new Verifier(`get_${name}`, mocking[`get_${name}`]), | |
writable: false, | |
enumerable: true | |
}, | |
verifySet: { | |
value: (name) => new Verifier(`set_${name}`, mocking[`set_${name}`]), | |
writable: false, | |
enumerable: true | |
} | |
}); | |
this.href = url; | |
} | |
function MockWindow(real, mockDefinition){ | |
function getValue(key){ | |
if(mockDefinition[key] && mockDefinition[key].get){ | |
return mockDefinition[key].get(); | |
} else { | |
return real[key]; | |
} | |
} | |
function setValue(key, value){ | |
if(mockDefinition[key] && mockDefinition[key].set){ | |
mockDefinition[key].set(value); | |
} else { | |
real[key] = value; | |
} | |
} | |
Object.keys(real) | |
.concat(Object.keys(mockDefinition)) | |
.filter((value, index, self) => self.indexOf(value) === index) | |
.forEach((key) => { | |
Object.defineProperty(this, key, { | |
enumerable: true, | |
get: function() { return getValue(key); }, | |
set: function(value) { setValue(key, value); } | |
}); | |
}); | |
} | |
exports.MockLocation = MockLocation; | |
exports.MockWindow = MockWindow; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment