merge to next

This commit is contained in:
Leo 2017-01-30 11:04:14 -05:00
commit 6ab2b6b6c3
31 changed files with 17724 additions and 1 deletions

228
test/mithril.js Normal file
View file

@ -0,0 +1,228 @@
describe("m.version()", function () {
"use strict"
it("exists", function () {
expect(m.version).to.be.a("function")
})
it("is a string", function () {
expect(m.version()).to.be.a("string")
})
})
describe("m()", function () {
"use strict"
it("exists", function () {
expect(m).to.be.a("function")
})
it("sets correct tag name", function () {
expect(m("div")).to.have.property("tag", "div")
})
it("sets correct tag name with only a class", function () {
expect(m(".foo")).to.have.property("tag", "div")
})
it("sets correct class name", function () {
expect(m(".foo")).to.have.deep.property("attrs.className", "foo")
})
it("sets correct tag name with only an attr", function () {
expect(m("[title=bar]")).to.have.property("tag", "div")
})
it("sets correct unquoted attr", function () {
expect(m("[title=bar]")).to.have.deep.property("attrs.title", "bar")
})
it("sets attr without a value as an empty string", function () {
expect(m("[empty]")).to.have.deep.property("attrs.empty", true)
})
it("sets correct single quoted attr", function () {
expect(m("[title=\'bar\']")).to.have.deep.property("attrs.title", "bar")
})
it("sets correct double quoted attr", function () {
expect(m("[title=\"bar\"]")).to.have.deep.property("attrs.title", "bar")
})
it("sets correct children with 1 string arg", function () {
expect(m("div", "test"))
.to.have.property("children")
.that.eqls(["test"])
})
it("sets correct children with multiple string args", function () {
expect(m("div", "test", "test2"))
.to.have.property("children")
.that.eqls(["test", "test2"])
})
it("sets correct children with string array", function () {
expect(m("div", ["test"]))
.to.have.property("children")
.that.eqls(["test"])
})
it("sets correct attrs with object", function () {
expect(m("div", {title: "bar"}, "test"))
.to.have.deep.property("attrs.title", "bar")
})
it("sets correct children with attrs object", function () {
expect(m("div", {title: "bar"}, "test"))
.to.have.property("children")
.that.eqls(["test"])
})
it("sets correct children with nested node", function () {
expect(m("div", {title: "bar"}, m("div")))
.to.have.property("children")
.that.eqls([m("div")])
})
it("sets correct children with string rest arg", function () {
expect(m("div", {title: "bar"}, "test0", "test1", "test2", "test3"))
.to.have.property("children")
.that.eqls(["test0", "test1", "test2", "test3"])
})
it("sets correct children with node rest arg", function () {
expect(m("div", {title: "bar"}, m("div"), m("i"), m("span")))
.to.have.property("children")
.that.eqls([m("div"), m("i"), m("span")])
})
it("sets correct children with string array & no attrs", function () {
expect(m("div", ["a", "b"]))
.to.have.property("children")
.that.eqls(["a", "b"])
})
it("sets correct children with node array & no attrs", function () {
expect(m("div", [m("div"), m("i")]))
.to.have.property("children")
.that.eqls([m("div"), m("i")])
})
it("sets correct children with 2nd arg as node", function () {
expect(m("div", m("div"))).to.have.property("children")
.that.eqls([m("div")])
})
it("sets correct tag with undefined array entry", function () {
expect(m("div", [undefined])).to.have.property("tag", "div")
})
it("loosely accepts invalid objects", function () {
expect(function () { m("div", [{foo: "bar"}]) }).to.not.throw()
})
it("accepts svg nodes", function () {
expect(m("svg", [m("g")]))
.to.have.property("children")
.that.eqls([m("g")])
})
it("accepts HTML children in svg element", function () {
expect(m("svg", [m("a[href='http://google.com']")]))
.to.have.property("children")
.that.eqls([m("a[href='http://google.com']")])
})
it("uses className if given", function () {
expect(m(".foo", {className: ""}))
.to.have.deep.property("attrs.className", "foo")
})
it("accepts a class and class attr", function () {
var node = m(".foo", {class: "bar"})
expect(node).to.have.deep.property("attrs.class")
expect(node.attrs.class).to.include("foo").and.include("bar")
})
it("accepts a class and className attr", function () {
var node = m(".foo", {className: "bar"})
expect(node).to.have.deep.property("attrs.className")
expect(node.attrs.className).to.include("foo").and.include("bar")
})
// https://github.com/lhorie/mithril.js/issues/382 and 512
it("sets an empty className attr if it's an empty string", function () {
expect(m("div", {className: ""}))
.to.have.deep.property("attrs.className", "")
})
it("does not set className attr if class is given", function () {
expect(m("div", {class: ""})).to.not.have.property("attrs.className")
})
it("does not set class attr if className is given", function () {
expect(m("div", {className: ""})).to.not.have.property("attrs.class")
})
it("sets an empty class attr if it's an empty string", function () {
expect(m("div", {class: ""})).to.have.deep.property("attrs.class", "")
})
it("does not flatten 1 nested array", function () {
expect(m("div", [1, 2, 3], 4))
.to.have.property("children")
.that.eqls([[1, 2, 3], 4])
})
it("does not flatten 2 nested arrays", function () {
expect(m("div", [1, 2, 3], [4, 5, 6, 7]))
.to.have.property("children")
.that.eqls([[1, 2, 3], [4, 5, 6, 7]])
})
it("does not flatten 3 nested arrays", function () {
expect(m("div", [1], [2], [3]))
.to.have.property("children")
.that.eqls([[1], [2], [3]])
})
it("doesn't recreate the DOM when classes are different", function () {
var v1 = m(".foo", {class: "", onclick: function () {}})
var v2 = m(".foo", {class: "bar", onclick: function () {}})
expect(v1)
.to.have.property("attrs")
.that.contains.all.keys("class", "onclick")
expect(v2)
.to.have.property("attrs")
.that.contains.all.keys("class", "onclick")
})
it("proxies an object first arg to m.component()", function () {
var spy = sinon.spy()
var component = {
controller: spy,
view: function () {
return m("div", "testing")
}
}
var args = {age: 12}
m(component, args).controller()
expect(spy.firstCall).to.have.been.calledWith(args)
m.component(component, args).controller()
expect(spy.secondCall).to.have.been.calledWith(args)
})
it("does not proxy to m.component() if the object does not have .view() method", function () {
var component = {}
var args = {age: 12}
expect(m.bind(m, component, args)).to.throw()
})
})

80
test/mithril.prop.js Normal file
View file

@ -0,0 +1,80 @@
describe("m.prop()", function () {
"use strict"
it("reads correct value", function () {
var prop = m.prop("test")
expect(prop()).to.equal("test")
})
it("defaults to `undefined`", function () {
var prop = m.prop()
expect(prop()).to.be.undefined
})
it("sets the correct value", function () {
var prop = m.prop("test")
prop("foo")
expect(prop()).to.equal("foo")
})
it("sets `null`", function () {
var prop = m.prop(null)
expect(prop()).to.be.null
})
it("sets `undefined`", function () {
var prop = m.prop(undefined)
expect(prop()).to.be.undefined
})
it("returns the new value when set", function () {
var prop = m.prop()
expect(prop("foo")).to.equal("foo")
})
it("correctly stringifies to the correct value", function () {
var prop = m.prop("test")
expect(JSON.stringify(prop)).to.equal('"test"')
})
it("correctly stringifies to the correct value as a child", function () {
var obj = {prop: m.prop("test")}
expect(JSON.stringify(obj)).to.equal('{"prop":"test"}')
})
it("correctly stringifies Date", function () {
var prop = m.prop(new Date(999))
expect(JSON.stringify(prop)).to.equal('"1970-01-01T00:00:00.999Z"')
})
it("correctly stringifies object with toJSON method", function () {
function Thing(name) {
this.name = name
}
Thing.prototype.toJSON = function() {
return {kind: 'Thing', name: this.name}
}
var banana = m.prop(new Thing("bannana"))
expect(JSON.stringify(banana)).to.equal('{"kind":"Thing","name":"bannana"}')
})
it("correctly wraps Mithril promises", function () {
var defer = m.deferred()
var prop = m.prop(defer.promise)
defer.resolve("test")
expect(prop()).to.equal("test")
})
it("returns a thenable when wrapping a Mithril promise", function () {
var defer = m.deferred()
var prop = m.prop(defer.promise).then(function () {
return "test2"
})
defer.resolve("test")
expect(prop()).to.equal("test2")
})
})

1613
test/mithril.render.js Normal file

File diff suppressed because it is too large Load diff

406
test/mithril.request.js Normal file
View file

@ -0,0 +1,406 @@
describe("m.request()", function () {
"use strict"
// Much easier to read
function resolve() {
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.$resolve.apply(xhr, arguments)
xhr.onreadystatechange()
return xhr
}
// Common abstraction: request(opts, ...callbacks)
function request(opts) {
var ret = m.request(opts)
for (var i = 0; i < arguments.length; i++) {
ret = ret.then(arguments[i])
}
resolve()
return ret
}
it("sets the correct properties on `GET`", function () {
var prop = request({
method: "GET",
url: "test"
})
expect(prop()).to.contain.keys({
method: "GET",
url: "test"
})
})
it("returns a Mithril promise (1)", function () {
var prop = request(
{method: "GET", url: "test"},
function () { return "foo" })
expect(prop()).to.equal("foo")
})
it("returns a Mithril promise (2)", function () {
var prop = request({method: "GET", url: "test"})
var result = prop()
expect(prop.then(function (value) { return value })()).to.equal(result)
})
it("sets the correct properties on `POST`", function () {
var prop = request({
method: "POST",
url: "http://domain.com:80",
data: {}
})
expect(prop()).to.contain.keys({
method: "POST",
url: "http://domain.com:80"
})
})
it("sets the correct arguments", function () {
expect(request({
method: "POST",
url: "http://domain.com:80/:test1",
data: {test1: "foo"}
})().url).to.equal("http://domain.com:80/foo")
})
it("propagates errors through the promise (1)", function () {
var error = m.prop()
var prop = m.request({
method: "GET",
url: "test",
deserialize: function () { throw new Error("error occurred") }
}).then(null, error)
resolve()
expect(prop().message).to.equal("error occurred")
expect(error().message).to.equal("error occurred")
})
it("propagates errors through the promise (2)", function () {
var error = m.prop()
var prop = m.request({
method: "GET",
url: "test",
deserialize: function () { throw new Error("error occurred") }
}).catch(error)
resolve()
expect(prop().message).to.equal("error occurred")
expect(error().message).to.equal("error occurred")
})
it("synchronously throws TypeErrors", function () {
var error = m.prop()
var exception
var prop = m.request({
method: "GET",
url: "test",
deserialize: function () { throw new TypeError("error occurred") }
}).then(null, error)
try {
resolve()
} catch (e) {
exception = e
}
expect(prop()).to.not.exist
expect(error()).to.not.exist
expect(exception.message).to.equal("error occurred")
})
it("sets correct Content-Type when given data", function () {
var error = m.prop()
m.request({
method: "POST",
url: "test",
data: {foo: 1}
}).then(null, error)
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.onreadystatechange()
expect(xhr.$headers).to.have.property(
"Content-Type",
"application/json; charset=utf-8")
})
it("doesn't set Content-Type when it doesn't have data", function () {
var error = m.prop()
m.request({
method: "POST",
url: "test"
}).then(null, error)
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.onreadystatechange()
expect(xhr.$headers).to.not.have.property("Content-Type")
})
it("sets xhr request headers as per the headers config", function () {
var error = m.prop()
m.request({
method: "POST",
url: "test",
headers: {
"Authorization" : "Bearer 12345abcd12345",
"CustomHeader" : "CustomValue"
}
}).then(null, error)
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.onreadystatechange()
expect(xhr.$headers).to.have.property(
"Authorization",
"Bearer 12345abcd12345")
expect(xhr.$headers).to.have.property(
"CustomHeader",
"CustomValue")
})
it("overwrites existing headers", function () {
var error = m.prop()
m.request({
method: "POST",
url: "test",
// Trigger the Content-Type addition
data: {foo: "bar"},
headers: {
"Authorization" : "Bearer 12345abcd12345",
"CustomHeader" : "CustomValue",
"Content-Type" : "CustomType"
}
}).then(null, error)
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.onreadystatechange()
expect(xhr.$headers).to.have.property(
"Authorization",
"Bearer 12345abcd12345")
expect(xhr.$headers).to.have.property(
"CustomHeader",
"CustomValue")
expect(xhr.$headers).to.have.property(
"Content-Type",
"CustomType")
})
it("correctly sets initial value", function () {
var prop = m.request({
method: "POST",
url: "test",
initialValue: "foo"
})
var initialValue = prop()
resolve()
expect(initialValue).to.equal("foo")
})
it("correctly propagates initial value when not completed", function () {
var prop = m.request({
method: "POST",
url: "test",
initialValue: "foo"
}).then(function (value) { return value })
var initialValue = prop()
resolve()
expect(initialValue).to.equal("foo")
})
it("resolves `then` correctly with an initialValue", function () {
var prop = m.request({
method: "POST",
url: "test",
initialValue: "foo"
}).then(function () { return "bar" })
resolve()
expect(prop()).to.equal("bar")
})
it("appends query strings to `url` from `data` for `GET`", function () {
var prop = m.request({method: "GET", url: "/test", data: {foo: 1}})
resolve()
expect(prop().url).to.equal("/test?foo=1")
})
it("doesn't append query strings to `url` from `data` for `POST`", function () { // eslint-disable-line
var prop = m.request({method: "POST", url: "/test", data: {foo: 1}})
resolve()
expect(prop().url).to.equal("/test")
})
it("ignores interpolations without data", function () { // eslint-disable-line
var prop = m.request({method: "GET", url: "/test:notfound", data: {foo: 1}})
resolve()
expect(prop().url).to.equal("/test:notfound?foo=1")
})
it("appends children in query strings to `url` from `data` for `GET`", function () { // eslint-disable-line
var prop = m.request({method: "GET", url: "test", data: {foo: [1, 2]}})
resolve()
expect(prop().url).to.equal("test?foo=1&foo=2")
})
it("propagates initial value in call before request is completed", function () { // eslint-disable-line
var value
var prop1 = m.request({method: "GET", url: "test", initialValue: 123})
expect(prop1()).to.equal(123)
var prop2 = prop1.then(function () { return 1 })
expect(prop2()).to.equal(123)
var prop3 = prop1.then(function (v) { value = v })
expect(prop3()).to.equal(123)
resolve()
expect(value.method).to.equal("GET")
expect(value.url).to.equal("test")
})
context("over jsonp", function () {
/* eslint-disable no-invalid-this */
beforeEach(function () {
var body = this.body = mock.document.createElement("body")
mock.document.body = body
mock.document.appendChild(body)
})
afterEach(function () {
mock.document.removeChild(this.body)
})
/* eslint-enable no-invalid-this */
function request(data, callbackKey) {
return m.request({
url: "/test",
dataType: "jsonp",
data: data,
callbackKey: callbackKey
})
}
function find(list, item, prop) {
var res
for (var i = 0; i < list.length; i++) {
var entry = list[i]
if (prop != null) entry = entry[prop]
if (entry.indexOf(item) >= 0) res = entry
}
return res
}
function resolve(data) {
var callback = find(Object.keys(mock), "mithril_callback")
var url = find(mock.document.getElementsByTagName("script"),
callback, "src")
mock[callback](data)
return url
}
it("sets the `GET` url with the correct query parameters", function () {
request({foo: "bar"})
expect(resolve({foo: "bar"})).to.contain("foo=bar")
})
it("correctly gets the value, without appending the script on the document", function () { // eslint-disable-line
var data = m.prop()
request().then(data)
var url = resolve({foo: "bar"})
expect(url).to.contain("/test?callback=mithril_callback")
expect(data()).to.eql({foo: "bar"})
})
it("correctly gets the value with a custom `callbackKey`, without appending the script on the document", function () { // eslint-disable-line
var data = m.prop()
request(null, "jsonpCallback").then(data)
var url = resolve({foo: "bar1"})
expect(url).to.contain("/test?jsonpCallback=mithril_callback")
expect(data()).to.eql({foo: "bar1"})
})
it("correctly gets the value on calling the function", function () {
var req = request()
resolve({foo: "bar1"})
expect(req()).to.eql({foo: "bar1"})
})
})
it("ends the computation when a SyntaxError is thrown from `options.extract`", function () { // eslint-disable-line max-len
var root = mock.document.createElement("div")
var viewSpy = sinon.spy(function () { return m("div") })
var resolved = sinon.spy()
var rejected = sinon.spy()
m.mount(root, {
controller: function () {
m.request({
url: "/test",
extract: function () {
throw new SyntaxError()
}
}).then(resolved, rejected)
},
view: viewSpy
})
// For good measure
mock.requestAnimationFrame.$resolve()
expect(function () {
resolve()
}).to.throw()
expect(resolved).to.not.have.been.called
expect(rejected).to.not.have.been.called
// The controller should throw, but the view should still render.
expect(viewSpy).to.have.been.called
// For good measure
mock.requestAnimationFrame.$resolve()
})
it("can use a config correctly", function () {
var config = sinon.spy()
var result = m.prop()
var error = sinon.spy
var opts = {
method: "GET",
url: "/test",
config: config
}
m.request(opts).then(result, error)
var xhr = resolve({foo: "bar"})
expect(config).to.be.calledWithExactly(xhr, opts)
expect(result()).to.eql({foo: "bar"})
expect(error).to.not.be.called
})
})

1210
test/mithril.route.js Normal file

File diff suppressed because it is too large Load diff

116
test/mithril.trust.js Normal file
View file

@ -0,0 +1,116 @@
describe("m.trust()", function () {
"use strict"
it("exists", function () {
expect(m.trust).to.be.a("function")
})
it("returns an instance of String", function () {
expect(m.trust("foo")).to.be.an.instanceof(String)
})
it("does not modify the string", function () {
expect(m.trust("foo").valueOf()).to.equal("foo")
})
it("is not identical to the string", function () {
expect(m.trust("foo")).to.not.equal("foo")
})
// FIXME: implement document.createRange().createContextualFragment() in the
// mock window for these tests
dom(function () {
it("isn't escaped in m.render()", function () {
var root = document.createElement("div")
m.render(root, m("div", "a", m.trust("&amp;"), "b"))
expect(root.childNodes[0].innerHTML).to.equal("a&amp;b")
})
it("works with mixed trusted content in div", function () {
var root = document.createElement("div")
m.render(root, [m.trust("<p>1</p><p>2</p>"), m("i", "foo")])
expect(root.childNodes[2].tagName).to.equal("I")
})
it("works with mixed trusted content in text nodes", function () {
var root = document.createElement("div")
m.render(root, [
m.trust("<p>1</p>123<p>2</p>"),
m("i", "foo")
])
expect(root.childNodes[3].tagName).to.equal("I")
})
it("works with mixed trusted content in td", function () {
var root = document.createElement("table")
root.appendChild(root = document.createElement("tr"))
m.render(root, [
m.trust("<td>1</td><td>2</td>"),
m("td", "foo")
])
expect(root.childNodes[2].tagName).to.equal("TD")
})
it("works with trusted content in div", function () {
var root = document.createElement("div")
m.render(root, m("div", [
m("p", "&copy;"),
m("p", m.trust("&copy;")),
m.trust("&copy;")
]))
expect(root.innerHTML)
.to.equal("<div><p>&amp;copy;</p><p>©</p>©</div>")
})
// https://github.com/lhorie/mithril.js/issues/1045
it("correctly injects script tags and executes them", function () {
var HTMLString =
"<script>document.getElementById('root').innerText='After'</script>"
var root = document.createElement("div")
var child = document.createElement("div")
root.id = "root"
root.innerText = "Before"
root.appendChild(child)
document.body.appendChild(root)
m.render(child, m.trust(HTMLString))
expect(root.innerText).to.equal("After")
})
// https://github.com/lhorie/mithril.js/issues/956
it("works with many and nested tags in trusted content", function () {
var page = {
names: m.prop(["John", "Paul", "George", "Ringo"]),
nodeString: function (name) {
return "<div><p>Hi </p></div><div><p>" + name + "</p></div>"
},
view: function () {
return m("div",
this.names().map(function (name) {
return m.trust(this.nodeString(name))
}, this)
)
}
}
m.render(document.body, page)
var root = document.body.children[0]
expect(root.children.length).to.equal(2 * page.names().length)
for (var i = 0; i < page.names().length; i++) {
var section = root.children[2 * i + 1]
expect(section.children[0].innerText).to.equal(page.names()[i])
}
page.names(["Jack", "Jill"])
m.render(document.body, page)
expect(root.children.length).to.equal(2 * page.names().length)
for (i = 0; i < page.names().length; i++) {
section = root.children[2 * i + 1]
expect(section.children[0].innerText).to.equal(page.names()[i])
}
})
})
})