Merge branch 'next'
This commit is contained in:
commit
2635070734
79 changed files with 7993 additions and 3507 deletions
|
|
@ -1,7 +1,7 @@
|
|||
.vscode
|
||||
/coverage
|
||||
/docs/lib
|
||||
/examples
|
||||
.vscode/
|
||||
coverage/
|
||||
docs/lib/
|
||||
examples/
|
||||
/mithril.js
|
||||
/mithril.min.js
|
||||
/node_modules
|
||||
node_modules/
|
||||
|
|
|
|||
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1,3 +1,5 @@
|
|||
* text=auto
|
||||
/mithril.js binary
|
||||
/mithril.min.js binary
|
||||
/package-lock.json binary
|
||||
/yarn.lock binary
|
||||
|
|
|
|||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
render/ @pygy
|
||||
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behavior
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
## Current Behavior
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Context
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Your Environment
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
* Version used:
|
||||
* Browser Name and version:
|
||||
* Operating System and version (desktop or mobile):
|
||||
* Link to your project:
|
||||
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
- [ ] Documentation change
|
||||
|
||||
## Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have read the **CONTRIBUTING** document.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
- [ ] I have updated `docs/change-log.md`
|
||||
|
|
@ -6,3 +6,4 @@
|
|||
.gitignore
|
||||
.travis.yml
|
||||
CONTRIBUTING.md
|
||||
yarn.lock
|
||||
|
|
|
|||
16
.travis.yml
16
.travis.yml
|
|
@ -13,15 +13,19 @@ cache:
|
|||
# Custom install step so the travis-only stuff doesn't need to be in package.json
|
||||
install:
|
||||
- npm install
|
||||
- npm install @alrra/travis-scripts@^3.0.1 gh-pages@^0.12.0
|
||||
# This is to prevent lint-staged/prettier from running on the bundles
|
||||
- npm rm husky
|
||||
|
||||
# Bundle before running tests so the bundle is always up-to-date
|
||||
before_script: npm run build --silent
|
||||
# Build bundles (so they're always up to date)
|
||||
before_script:
|
||||
- npm run build-browser
|
||||
# Pass -save so it'll update the readme as well
|
||||
- npm run build-min -- -save
|
||||
|
||||
# Run tests, lint, and then check for perf regressions
|
||||
script:
|
||||
- npm test --silent
|
||||
- npm run perf --silent
|
||||
- npm test
|
||||
- npm run perf
|
||||
|
||||
# After a successful build commit changes back to repo
|
||||
after_success:
|
||||
|
|
@ -84,7 +88,6 @@ deploy:
|
|||
on:
|
||||
tags: true
|
||||
repo: MithrilJS/mithril.js
|
||||
branch: master
|
||||
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
|
|
@ -94,4 +97,3 @@ deploy:
|
|||
on:
|
||||
tags: true
|
||||
repo: MithrilJS/mithril.js
|
||||
branch: master
|
||||
|
|
|
|||
316
README.md
316
README.md
|
|
@ -1,267 +1,61 @@
|
|||
# Introduction
|
||||
mithril.js [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril)
|
||||
==========
|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/MithrilJS/mithril.js">
|
||||
<img src="https://img.shields.io/travis/MithrilJS/mithril.js/next.svg" alt="Build Status">
|
||||
</a>
|
||||
<a href="https://gitter.im/mithriljs/mithril.js">
|
||||
<img src="https://img.shields.io/gitter/room/mithriljs/mithril.js.svg" alt="Gitter" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- [What is Mithril?](#what-is-mithril)
|
||||
- [Getting started](#getting-started)
|
||||
- [Hello world](#hello-world)
|
||||
- [DOM elements](#dom-elements)
|
||||
- [Components](#components)
|
||||
- [Routing](#routing)
|
||||
- [XHR](#xhr)
|
||||
- [Installation](#installation)
|
||||
- [Documentation](#documentation)
|
||||
- [Getting Help](#getting-help)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## What is Mithril?
|
||||
|
||||
A modern client-side Javascript framework for building Single Page Applications. It's small (<!-- size -->8.87 KB<!-- /size --> gzipped), fast and provides routing and XHR utilities out of the box.
|
||||
|
||||
Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍.
|
||||
|
||||
Browsers all the way back to IE9 are supported, no polyfills required 👌.
|
||||
|
||||
## Installation
|
||||
|
||||
### CDN
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/mithril"></script>
|
||||
```
|
||||
|
||||
### npm
|
||||
|
||||
```bash
|
||||
$ npm install mithril
|
||||
```
|
||||
|
||||
The ["Getting started" guide](https://mithril.js.org/#getting-started) is a good place to start learning how to use mithril.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation lives on [mithril.js.org](https://mithril.js.org).
|
||||
|
||||
You may be interested in the [API Docs](https://mithril.js.org/api.html), a [Simple Application](https://mithril.js.org/simple-application.html), or perhaps some [Examples](https://mithril.js.org/examples.html).
|
||||
|
||||
## Getting Help
|
||||
|
||||
Mithril has an active & welcoming community on [Gitter](https://gitter.im/mithriljs/mithril.js), or feel free to ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/mithril.js) using the `mithril.js` tag.
|
||||
|
||||
## Contributing
|
||||
|
||||
There's a [Contributing FAQ](https://mithril.js.org/contributing.html) on the mithril site that hopefully helps, but if not definitely hop into the [Gitter Room](https://gitter.im/mithriljs/mithril.js) and ask away!
|
||||
|
||||
---
|
||||
|
||||
### What is Mithril?
|
||||
Thanks for reading!
|
||||
|
||||
Mithril is a modern client-side Javascript framework for building Single Page Applications.
|
||||
It's small (< 8kb gzip), fast and provides routing and XHR utilities out of the box.
|
||||
|
||||
<div style="display:flex;margin:0 0 30px;">
|
||||
<div style="width:50%;">
|
||||
<h5>Download size</h5>
|
||||
<small>Mithril (8kb)</small>
|
||||
<div style="animation:grow 0.08s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:4%;"></div>
|
||||
<small style="color:#aaa;">Vue + Vue-Router + Vuex + fetch (40kb)</small>
|
||||
<div style="animation:grow 0.4s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:20%"></div>
|
||||
<small style="color:#aaa;">React + React-Router + Redux + fetch (64kb)</small>
|
||||
<div style="animation:grow 0.64s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:32%"></div>
|
||||
<small style="color:#aaa;">Angular (135kb)</small>
|
||||
<div style="animation:grow 1.35s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:68%"></div>
|
||||
</div>
|
||||
<div style="width:50%;">
|
||||
<h5>Performance</h5>
|
||||
<small>Mithril (6.4ms)</small>
|
||||
<div style="animation:grow 0.64s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:24%;"></div>
|
||||
<small style="color:#aaa;">Vue (9.8ms)</small>
|
||||
<div style="animation:grow 0.98s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:40%"></div>
|
||||
<small style="color:#aaa;">React (12.1ms)</small>
|
||||
<div style="animation:grow 1.21s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:48%"></div>
|
||||
<small style="color:#aaa;">Angular (11.5ms)</small>
|
||||
<div style="animation:grow 1.15s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:44%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess.
|
||||
|
||||
If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](http://mithril.js.org/framework-comparison.html) page.
|
||||
|
||||
Mithril supports browsers all the way back to IE9, no polyfills required.
|
||||
|
||||
---
|
||||
|
||||
### Getting started
|
||||
|
||||
The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll cover the majority of the API surface (including routing and XHR) but it'll only take 10 minutes.
|
||||
|
||||
Let's create an HTML file to follow along:
|
||||
|
||||
```markup
|
||||
<body>
|
||||
<script src="//unpkg.com/mithril/mithril.js"></script>
|
||||
<script>
|
||||
var root = document.body
|
||||
|
||||
// your code goes here!
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Hello world
|
||||
|
||||
Let's start as small as we can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better)
|
||||
|
||||
```javascript
|
||||
var root = document.body
|
||||
|
||||
m.render(root, "Hello world")
|
||||
```
|
||||
|
||||
Now, let's change the text to something else. Add this line of code under the previous one:
|
||||
|
||||
```javascript
|
||||
m.render(root, "My first app")
|
||||
```
|
||||
|
||||
As you can see, you use the same code to both create and update HTML. Mithril automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch.
|
||||
|
||||
---
|
||||
|
||||
### DOM elements
|
||||
|
||||
Let's wrap our text in an `<h1>` tag.
|
||||
|
||||
```javascript
|
||||
m.render(root, m("h1", "My first app"))
|
||||
```
|
||||
|
||||
The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the `<h1>`:
|
||||
|
||||
```javascript
|
||||
m("h1", {class: "title"}, "My first app")
|
||||
```
|
||||
|
||||
If you want to have multiple elements:
|
||||
|
||||
```javascript
|
||||
[
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
]
|
||||
```
|
||||
|
||||
And so on:
|
||||
|
||||
```javascript
|
||||
m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
])
|
||||
```
|
||||
|
||||
Note: If you prefer `<html>` syntax, [it's possible to use it via a Babel plugin](http://mithril.js.org/jsx.html).
|
||||
|
||||
```jsx
|
||||
// HTML syntax via Babel's JSX plugin
|
||||
<main>
|
||||
<h1 class="title">My first app</h1>
|
||||
<button>A button</button>
|
||||
</main>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Components
|
||||
|
||||
A Mithril component is just an object with a `view` function. Here's the code above as a component:
|
||||
|
||||
```javascript
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To activate the component, we use `m.mount`.
|
||||
|
||||
```javascript
|
||||
m.mount(root, Hello)
|
||||
```
|
||||
|
||||
As you would expect, doing so creates this markup:
|
||||
|
||||
```markup
|
||||
<main>
|
||||
<h1 class="title">My first app</h1>
|
||||
<button>A button</button>
|
||||
</main>
|
||||
```
|
||||
|
||||
The `m.mount` function is similar to `m.render`, but instead of rendering some HTML only once, it activates Mithril's auto-redrawing system. To understand what that means, let's add some events:
|
||||
|
||||
```javascript
|
||||
var count = 0 // added a variable
|
||||
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
// changed the next line
|
||||
m("button", {onclick: function() {count++}}, count + " clicks"),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(root, Hello)
|
||||
```
|
||||
|
||||
We defined an `onclick` event on the button, which increments a variable `count` (which was declared at the top). We are now also rendering the value of that variable in the button label.
|
||||
|
||||
You can now update the label of the button by clicking the button. Since we used `m.mount`, you don't need to manually call `m.render` to apply the changes in the `count` variable to the HTML; Mithril does it for you.
|
||||
|
||||
If you're wondering about performance, it turns out Mithril is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril actually updates.
|
||||
|
||||
---
|
||||
|
||||
### Routing
|
||||
|
||||
Routing just means going from one screen to another in an application with several screens.
|
||||
|
||||
Let's add a splash page that appears before our click counter. First we create a component for it:
|
||||
|
||||
```javascript
|
||||
var Splash = {
|
||||
view: function() {
|
||||
return m("a", {href: "#!/hello"}, "Enter!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, this component simply renders a link to `#!/hello`. The `#!` part is known as a hashbang, and it's a common convention used in Single Page Applications to indicate that the stuff after it (the `/hello` part) is a route path.
|
||||
|
||||
Now that we going to have more than one screen, we use `m.route` instead of `m.mount`.
|
||||
|
||||
```javascript
|
||||
m.route(root, "/splash", {
|
||||
"/splash": Splash,
|
||||
"/hello": Hello,
|
||||
})
|
||||
```
|
||||
|
||||
The `m.route` function still has the same auto-redrawing functionality that `m.mount` does, and it also enables URL awareness; in other words, it lets Mithril know what to do when it sees a `#!` in the URL.
|
||||
|
||||
The `"/splash"` right after `root` means that's the default route, i.e. if the hashbang in the URL doesn't point to one of the defined routes (`/splash` and `/hello`, in our case), then Mithril redirects to the default route. So if you open the page in a browser and your URL is `http://localhost`, then you get redirected to `http://localhost/#!/splash`.
|
||||
|
||||
Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `http://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button.
|
||||
|
||||
---
|
||||
|
||||
### XHR
|
||||
|
||||
Basically, XHR is just a way to talk to a server.
|
||||
|
||||
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial.
|
||||
|
||||
First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work)
|
||||
|
||||
```javascript
|
||||
var count = 0
|
||||
var increment = function() {
|
||||
m.request({
|
||||
method: "PUT",
|
||||
url: "//rem-rest-api.herokuapp.com/api/tutorial/1",
|
||||
data: {count: count + 1},
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(data) {
|
||||
count = parseInt(data.count)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Calling the increment function [upserts](https://en.wiktionary.org/wiki/upsert) an object `{count: 1}` to the `/api/tutorial/1` endpoint. This endpoint returns an object with the same `count` value that was sent to it. Notice that the `count` variable is only updated after the request completes, and it's updated with the response value from the server now.
|
||||
|
||||
Let's replace the event handler in the component to call the `increment` function instead of incrementing the `count` variable directly:
|
||||
|
||||
```javascript
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", {onclick: increment}, count + " clicks"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clicking the button should now update the count.
|
||||
|
||||
---
|
||||
|
||||
We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR.
|
||||
|
||||
This should be enough to get you started writing the frontend for a real application. Now that you are comfortable with the basics of the Mithril API, [be sure to check out the simple application tutorial](http://mithril.js.org/simple-application.html), which walks you through building a realistic application.
|
||||
🎁
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ module.exports = function(redrawService) {
|
|||
redrawService.render(root, Vnode(component))
|
||||
}
|
||||
redrawService.subscribe(root, run)
|
||||
redrawService.redraw()
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,26 +4,23 @@ var coreRenderer = require("../render/render")
|
|||
|
||||
function throttle(callback) {
|
||||
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
||||
var time = 16
|
||||
var delay = 16
|
||||
var last = 0, pending = null
|
||||
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
||||
return function() {
|
||||
var now = Date.now()
|
||||
if (last === 0 || now - last >= time) {
|
||||
last = now
|
||||
callback()
|
||||
}
|
||||
else if (pending === null) {
|
||||
var elapsed = Date.now() - last
|
||||
if (pending === null) {
|
||||
pending = timeout(function() {
|
||||
pending = null
|
||||
callback()
|
||||
last = Date.now()
|
||||
}, time - (now - last))
|
||||
}, delay - elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function($window) {
|
||||
|
||||
module.exports = function($window, throttleMock) {
|
||||
var renderService = coreRenderer($window)
|
||||
renderService.setEventCallback(function(e) {
|
||||
if (e.redraw === false) e.redraw = undefined
|
||||
|
|
@ -31,18 +28,24 @@ module.exports = function($window) {
|
|||
})
|
||||
|
||||
var callbacks = []
|
||||
var rendering = false
|
||||
|
||||
function subscribe(key, callback) {
|
||||
unsubscribe(key)
|
||||
callbacks.push(key, throttle(callback))
|
||||
callbacks.push(key, callback)
|
||||
}
|
||||
function unsubscribe(key) {
|
||||
var index = callbacks.indexOf(key)
|
||||
if (index > -1) callbacks.splice(index, 2)
|
||||
}
|
||||
function redraw() {
|
||||
for (var i = 1; i < callbacks.length; i += 2) {
|
||||
callbacks[i]()
|
||||
}
|
||||
function sync() {
|
||||
if (rendering) throw new Error("Nested m.redraw.sync() call")
|
||||
rendering = true
|
||||
for (var i = 1; i < callbacks.length; i+=2) try {callbacks[i]()} catch (e) {if (typeof console !== "undefined") console.error(e)}
|
||||
rendering = false
|
||||
}
|
||||
|
||||
var redraw = (throttleMock || throttle)(sync)
|
||||
redraw.sync = sync
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,14 @@ module.exports = function($window, redrawService) {
|
|||
var render, component, attrs, currentPath, lastUpdate
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
var run = function() {
|
||||
function run() {
|
||||
if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
|
||||
}
|
||||
var redraw = function() {
|
||||
run()
|
||||
redraw = redrawService.redraw
|
||||
}
|
||||
redrawService.subscribe(root, run)
|
||||
var bail = function(path) {
|
||||
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
|
||||
else throw new Error("Could not resolve default route " + defaultRoute)
|
||||
|
|
@ -24,7 +29,7 @@ module.exports = function($window, redrawService) {
|
|||
component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
|
||||
attrs = params, currentPath = path, lastUpdate = null
|
||||
render = (routeResolver.render || identity).bind(routeResolver)
|
||||
run()
|
||||
redraw()
|
||||
}
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
|
|
@ -36,7 +41,6 @@ module.exports = function($window, redrawService) {
|
|||
else update(payload, "div")
|
||||
}
|
||||
}, bail)
|
||||
redrawService.subscribe(root, run)
|
||||
}
|
||||
route.set = function(path, data, options) {
|
||||
if (lastUpdate != null) {
|
||||
|
|
@ -48,7 +52,7 @@ module.exports = function($window, redrawService) {
|
|||
}
|
||||
route.get = function() {return currentPath}
|
||||
route.prefix = function(prefix) {routeService.prefix = prefix}
|
||||
route.link = function(vnode) {
|
||||
var link = function(options, vnode) {
|
||||
vnode.dom.setAttribute("href", routeService.prefix + vnode.attrs.href)
|
||||
vnode.dom.onclick = function(e) {
|
||||
if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
|
||||
|
|
@ -56,9 +60,13 @@ module.exports = function($window, redrawService) {
|
|||
e.redraw = false
|
||||
var href = this.getAttribute("href")
|
||||
if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
|
||||
route.set(href, undefined, undefined)
|
||||
route.set(href, undefined, options)
|
||||
}
|
||||
}
|
||||
route.link = function(args) {
|
||||
if (args.tag == null) return link.bind(link, args)
|
||||
return link({}, args)
|
||||
}
|
||||
route.param = function(key) {
|
||||
if(typeof attrs !== "undefined" && typeof key !== "undefined") return attrs[key]
|
||||
return attrs
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
<script src="../../test-utils/xhrMock.js"></script>
|
||||
<script src="../../test-utils/browserMock.js"></script>
|
||||
<script src="../../test-utils/component.js"></script>
|
||||
|
||||
<script src="../../test-utils/components.js"></script>
|
||||
<script src="../../test-utils/throttleMock.js"></script>
|
||||
<script src="../../promise/promise.js"></script>
|
||||
<script src="../../render/vnode.js"></script>
|
||||
<script src="../../render/trust.js"></script>
|
||||
|
|
@ -28,7 +28,6 @@
|
|||
<script src="../../api/redraw.js"></script>
|
||||
<script src="../../api/mount.js"></script>
|
||||
<script src="../../api/router.js"></script>
|
||||
|
||||
<script src="./test-redraw.js"></script>
|
||||
<script src="./test-mount.js"></script>
|
||||
<script src="./test-router.js"></script>
|
||||
|
|
|
|||
|
|
@ -3,25 +3,29 @@
|
|||
var o = require("../../ospec/ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiMounter = require("../../api/mount")
|
||||
|
||||
o.spec("mount", function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, redrawService, mount, render
|
||||
var $window, root, redrawService, mount, render, throttleMock
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
throttleMock = throttleMocker()
|
||||
|
||||
root = $window.document.body
|
||||
|
||||
redrawService = apiRedraw($window)
|
||||
redrawService = apiRedraw($window, throttleMock.throttle)
|
||||
mount = apiMounter(redrawService)
|
||||
render = redrawService.render
|
||||
})
|
||||
|
||||
o.afterEach(function() {
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
})
|
||||
|
||||
o("throws on invalid component", function() {
|
||||
var threw = false
|
||||
try {
|
||||
|
|
@ -46,7 +50,7 @@ o.spec("mount", function() {
|
|||
o(threw).equals(true)
|
||||
})
|
||||
|
||||
o("renders into `root`", function() {
|
||||
o("renders into `root` synchronoulsy", function() {
|
||||
mount(root, createComponent({
|
||||
view : function() {
|
||||
return m("div")
|
||||
|
|
@ -68,7 +72,37 @@ o.spec("mount", function() {
|
|||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
o("Mounting a second root doesn't cause the first one to redraw", function() {
|
||||
var view = o.spy(function() {
|
||||
return m("div")
|
||||
})
|
||||
|
||||
render(root, [
|
||||
m("#child0"),
|
||||
m("#child1")
|
||||
])
|
||||
|
||||
mount(root.childNodes[0], createComponent({
|
||||
view : view
|
||||
}))
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
mount(root.childNodes[1], createComponent({
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
}))
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws on events", function() {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
|
|
@ -96,17 +130,12 @@ o.spec("mount", function() {
|
|||
o(onclick.args[0].type).equals("click")
|
||||
o(onclick.args[0].target).equals(root.firstChild)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
throttleMock.fire()
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws several mount points on events", function(done, timeout) {
|
||||
timeout(60)
|
||||
|
||||
o("redraws several mount points on events", function() {
|
||||
var onupdate0 = o.spy()
|
||||
var oninit0 = o.spy()
|
||||
var onclick0 = o.spy()
|
||||
|
|
@ -153,26 +182,26 @@ o.spec("mount", function() {
|
|||
o(onclick0.callCount).equals(1)
|
||||
o(onclick0.this).equals(root.childNodes[0].firstChild)
|
||||
|
||||
setTimeout(function() {
|
||||
o(onupdate0.callCount).equals(1)
|
||||
o(onupdate1.callCount).equals(1)
|
||||
throttleMock.fire()
|
||||
|
||||
root.childNodes[1].firstChild.dispatchEvent(e)
|
||||
o(onclick1.callCount).equals(1)
|
||||
o(onclick1.this).equals(root.childNodes[1].firstChild)
|
||||
o(onupdate0.callCount).equals(1)
|
||||
o(onupdate1.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(onupdate0.callCount).equals(2)
|
||||
o(onupdate1.callCount).equals(2)
|
||||
root.childNodes[1].firstChild.dispatchEvent(e)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
o(onclick1.callCount).equals(1)
|
||||
o(onclick1.this).equals(root.childNodes[1].firstChild)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(onupdate0.callCount).equals(2)
|
||||
o(onupdate1.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
var onupdate = o.spy()
|
||||
o("event handlers can skip redraw", function() {
|
||||
var onupdate = o.spy(function(){
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
var oninit = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
|
|
@ -194,15 +223,12 @@ o.spec("mount", function() {
|
|||
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
throttleMock.fire()
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("redraws when the render function is run", function(done) {
|
||||
o("redraws when the render function is run", function() {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
|
||||
|
|
@ -220,17 +246,12 @@ o.spec("mount", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
throttleMock.fire()
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
o("throttles", function() {
|
||||
var i = 0
|
||||
mount(root, createComponent({view: function() {i++}}))
|
||||
var before = i
|
||||
|
|
@ -242,12 +263,11 @@ o.spec("mount", function() {
|
|||
|
||||
var after = i
|
||||
|
||||
setTimeout(function(){
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
done()
|
||||
},40)
|
||||
throttleMock.fire()
|
||||
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
var o = require("../../ospec/ospec")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
|
||||
o.spec("redrawService", function() {
|
||||
|
|
@ -17,25 +18,39 @@ o.spec("redrawService", function() {
|
|||
redrawService.redraw()
|
||||
})
|
||||
|
||||
o("honours throttleMock", function() {
|
||||
var throttleMock = throttleMocker()
|
||||
redrawService = apiRedraw(domMock(), throttleMock.throttle)
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("should run a single renderer entry", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.callCount).equals(0)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
|
@ -54,27 +69,29 @@ o.spec("redrawService", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should stop running after unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
o("should stop running after unsubscribe", function(done) {
|
||||
var spy = o.spy(function() {
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
redrawService.unsubscribe(root, spy)
|
||||
|
|
@ -82,9 +99,33 @@ o.spec("redrawService", function() {
|
|||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("does nothing on invalid unsubscribe", function() {
|
||||
o("should stop running after unsubscribe, even if it occurs after redraw is requested", function(done) {
|
||||
var spy = o.spy(function() {
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
redrawService.unsubscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("does nothing on invalid unsubscribe", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
|
@ -92,6 +133,39 @@ o.spec("redrawService", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("redraw.sync() redraws all roots synchronously", function() {
|
||||
var el1 = $document.createElement("div")
|
||||
var el2 = $document.createElement("div")
|
||||
var el3 = $document.createElement("div")
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
var spy3 = o.spy()
|
||||
|
||||
redrawService.subscribe(el1, spy1)
|
||||
redrawService.subscribe(el2, spy2)
|
||||
redrawService.subscribe(el3, spy3)
|
||||
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var browserMock = require("../../test-utils/browserMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
|
|
@ -14,19 +15,23 @@ o.spec("route", function() {
|
|||
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
|
||||
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
|
||||
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, redrawService, route
|
||||
var $window, root, redrawService, route, throttleMock
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = browserMock(env)
|
||||
throttleMock = throttleMocker()
|
||||
|
||||
root = $window.document.body
|
||||
|
||||
redrawService = apiRedraw($window)
|
||||
redrawService = apiRedraw($window, throttleMock.throttle)
|
||||
route = apiRouter($window, redrawService)
|
||||
route.prefix(prefix)
|
||||
})
|
||||
|
||||
o.afterEach(function() {
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
})
|
||||
|
||||
o("throws on invalid `root` DOM node", function() {
|
||||
var threw = false
|
||||
try {
|
||||
|
|
@ -50,7 +55,7 @@ o.spec("route", function() {
|
|||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (POJO component)", function() {
|
||||
o("routed mount points only redraw asynchronously (POJO component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
|
|
@ -60,11 +65,14 @@ o.spec("route", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (constructible component)", function() {
|
||||
o("routed mount points only redraw asynchronously (constructible component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
var Cmp = function(){}
|
||||
|
|
@ -77,11 +85,14 @@ o.spec("route", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (closure component)", function() {
|
||||
o("routed mount points only redraw asynchronously (closure component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
function Cmp() {return {view: view}}
|
||||
|
|
@ -93,8 +104,11 @@ o.spec("route", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("default route doesn't break back button", function(done) {
|
||||
|
|
@ -160,11 +174,12 @@ o.spec("route", function() {
|
|||
o(oninit.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
o("redraws on events", function() {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
|
|
@ -194,12 +209,9 @@ o.spec("route", function() {
|
|||
o(onclick.args[0].type).equals("click")
|
||||
o(onclick.args[0].target).equals(root.firstChild)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
callAsync(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
})
|
||||
throttleMock.fire()
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
|
|
@ -269,6 +281,36 @@ o.spec("route", function() {
|
|||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
|
||||
})
|
||||
|
||||
o("passes options on route.link", function() {
|
||||
var opts = {}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
route(root, "/", {
|
||||
"/" : {
|
||||
view: function() {
|
||||
return m("a", {
|
||||
href: "/test",
|
||||
oncreate: route.link(opts)
|
||||
})
|
||||
}
|
||||
},
|
||||
"/test" : {
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
})
|
||||
route.set = o.spy(route.set)
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(route.set.callCount).equals(1)
|
||||
o(route.set.args[2]).equals(opts)
|
||||
})
|
||||
|
||||
o("accepts RouteResolver with onmatch that returns Component", function(done) {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
|
|
@ -502,7 +544,10 @@ o.spec("route", function() {
|
|||
o(oninit.callCount).equals(1)
|
||||
route.set("/def")
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(oninit.callCount).equals(2)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
|
@ -538,23 +583,28 @@ o.spec("route", function() {
|
|||
route(root, "/a", {
|
||||
"/a" : {
|
||||
render: function() {
|
||||
return m("div")
|
||||
return m("div", m("p"))
|
||||
},
|
||||
},
|
||||
"/b" : {
|
||||
render: function() {
|
||||
return m("div")
|
||||
return m("div", m("a"))
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
var dom = root.firstChild
|
||||
var child = dom.firstChild
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
route.set("/b")
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(root.firstChild).equals(dom)
|
||||
o(root.firstChild.firstChild).notEquals(child)
|
||||
|
||||
done()
|
||||
})
|
||||
|
|
@ -588,6 +638,7 @@ o.spec("route", function() {
|
|||
o(renderCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
|
|
@ -623,6 +674,7 @@ o.spec("route", function() {
|
|||
o(renderCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
|
|
@ -818,10 +870,14 @@ o.spec("route", function() {
|
|||
})
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
route.set("/b")
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(render.callCount).equals(0)
|
||||
o(component.view.callCount).equals(2)
|
||||
|
||||
|
|
@ -942,6 +998,7 @@ o.spec("route", function() {
|
|||
o(onmatch.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
|
@ -1020,6 +1077,8 @@ o.spec("route", function() {
|
|||
})
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(onmatch.callCount).equals(1)
|
||||
o(render.callCount).equals(1)
|
||||
|
||||
|
|
@ -1027,6 +1086,8 @@ o.spec("route", function() {
|
|||
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(onmatch.callCount).equals(2)
|
||||
o(render.callCount).equals(2)
|
||||
|
||||
|
|
@ -1077,9 +1138,15 @@ o.spec("route", function() {
|
|||
route.set("/b")
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(root.firstChild.nodeName).equals("B")
|
||||
|
||||
route.set("/a")
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(root.firstChild.nodeName).equals("A")
|
||||
|
||||
done()
|
||||
|
|
@ -1144,7 +1211,9 @@ o.spec("route", function() {
|
|||
|
||||
route.set("/b")
|
||||
|
||||
// setting the route is asynchronous
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
|
|
@ -1184,9 +1253,7 @@ o.spec("route", function() {
|
|||
})
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
o("throttles", function() {
|
||||
var i = 0
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
|
|
@ -1200,12 +1267,11 @@ o.spec("route", function() {
|
|||
redrawService.redraw()
|
||||
var after = i
|
||||
|
||||
setTimeout(function() {
|
||||
o(before).equals(1) // routes synchronously
|
||||
o(after).equals(2) // redraws synchronously
|
||||
o(i).equals(3) // throttles rest
|
||||
done()
|
||||
}, FRAME_BUDGET * 2)
|
||||
throttleMock.fire()
|
||||
|
||||
o(before).equals(1) // routes synchronously
|
||||
o(after).equals(1) // redraws asynchronously
|
||||
o(i).equals(2)
|
||||
})
|
||||
|
||||
o("m.route.param is available outside of route handlers", function(done) {
|
||||
|
|
|
|||
|
|
@ -17,113 +17,109 @@ function parse(file) {
|
|||
|
||||
var error
|
||||
function run(input, output) {
|
||||
try {
|
||||
var modules = {}
|
||||
var bindings = {}
|
||||
var declaration = /^\s*(?:var|let|const|function)[\t ]+([\w_$]+)/gm
|
||||
var include = /(?:((?:var|let|const|,|)[\t ]*)([\w_$\.\[\]"'`]+)(\s*=\s*))?require\(([^\)]+)\)(\s*[`\.\(\[])?/gm
|
||||
var uuid = 0
|
||||
var process = function(filepath, data) {
|
||||
data.replace(declaration, function(match, binding) {bindings[binding] = 0})
|
||||
|
||||
return data.replace(include, function(match, def, variable, eq, dep, rest) {
|
||||
var filename = new Function("return " + dep).call(), pre = ""
|
||||
|
||||
def = def || "", variable = variable || "", eq = eq || "", rest = rest || ""
|
||||
if (def[0] === ",") def = "\nvar ", pre = "\n"
|
||||
var dependency = resolve(filepath, filename)
|
||||
var localUUID = uuid // global uuid can update from nested `process` call, ensure same id is used on declaration and consumption
|
||||
var code = process(dependency, pre + (modules[dependency] == null ? exportCode(filename, dependency, def, variable, eq, rest, localUUID) : def + variable + eq + modules[dependency]))
|
||||
modules[dependency] = rest ? "_" + localUUID : variable
|
||||
uuid++
|
||||
return code + rest
|
||||
})
|
||||
var modules = {}
|
||||
var bindings = {}
|
||||
var declaration = /^\s*(?:var|let|const|function)[\t ]+([\w_$]+)/gm
|
||||
var include = /(?:((?:var|let|const|,|)[\t ]*)([\w_$\.\[\]"'`]+)(\s*=\s*))?require\(([^\)]+)\)(\s*[`\.\(\[])?/gm
|
||||
var uuid = 0
|
||||
var process = function(filepath, data) {
|
||||
data.replace(declaration, function(match, binding) {bindings[binding] = 0})
|
||||
|
||||
return data.replace(include, function(match, def, variable, eq, dep, rest) {
|
||||
var filename = new Function("return " + dep).call(), pre = ""
|
||||
|
||||
def = def || "", variable = variable || "", eq = eq || "", rest = rest || ""
|
||||
if (def[0] === ",") def = "\nvar ", pre = "\n"
|
||||
var dependency = resolve(filepath, filename)
|
||||
var localUUID = uuid // global uuid can update from nested `process` call, ensure same id is used on declaration and consumption
|
||||
var code = process(dependency, pre + (modules[dependency] == null ? exportCode(filename, dependency, def, variable, eq, rest, localUUID) : def + variable + eq + modules[dependency]))
|
||||
modules[dependency] = rest ? "_" + localUUID : variable
|
||||
uuid++
|
||||
return code + rest
|
||||
})
|
||||
}
|
||||
|
||||
var resolve = function(filepath, filename) {
|
||||
if (filename[0] !== ".") {
|
||||
// resolve as npm dependency
|
||||
var packagePath = "./node_modules/" + filename + "/package.json"
|
||||
var meta = isFile(packagePath) ? parse(packagePath) : {}
|
||||
var main = "./node_modules/" + filename + "/" + (meta.main || filename + ".js")
|
||||
return path.resolve(isFile(main) ? main : "./node_modules/" + filename + "/index.js")
|
||||
}
|
||||
|
||||
var resolve = function(filepath, filename) {
|
||||
if (filename[0] !== ".") {
|
||||
// resolve as npm dependency
|
||||
var packagePath = "./node_modules/" + filename + "/package.json"
|
||||
var meta = isFile(packagePath) ? parse(packagePath) : {}
|
||||
var main = "./node_modules/" + filename + "/" + (meta.main || filename + ".js")
|
||||
return path.resolve(isFile(main) ? main : "./node_modules/" + filename + "/index.js")
|
||||
}
|
||||
else {
|
||||
// resolve as local dependency
|
||||
return path.resolve(path.dirname(filepath), filename + ".js")
|
||||
}
|
||||
}
|
||||
|
||||
var exportCode = function(filename, filepath, def, variable, eq, rest, uuid) {
|
||||
var code = read(filepath)
|
||||
// if there's a syntax error, report w/ proper stack trace
|
||||
try {new Function(code)} catch (e) {
|
||||
proc.exec("node " + filepath, function(e) {
|
||||
if (e !== null && e.message !== error) {
|
||||
error = e.message
|
||||
console.log("\x1b[31m" + e.message + "\x1b[0m")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// disambiguate collisions
|
||||
var ignored = {}
|
||||
code.replace(include, function(match, def, variable, eq, dep) {
|
||||
var filename = new Function("return " + dep).call()
|
||||
var binding = modules[resolve(filepath, filename)]
|
||||
if (binding != null) ignored[binding] = true
|
||||
})
|
||||
if (code.match(new RegExp("module\\.exports\\s*=\\s*" + variable + "\s*$", "m"))) ignored[variable] = true
|
||||
for (var binding in bindings) {
|
||||
if (!ignored[binding]) {
|
||||
var before = code
|
||||
code = code.replace(new RegExp("(\\b)" + binding + "\\b", "g"), binding + bindings[binding])
|
||||
if (before !== code) bindings[binding]++
|
||||
}
|
||||
}
|
||||
|
||||
// fix strings that got mangled by collision disambiguation
|
||||
var string = /(["'])((?:\\\1|.)*?)(\1)/g
|
||||
var candidates = Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|")
|
||||
code = code.replace(string, function(match, open, data, close) {
|
||||
var variables = new RegExp(Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|"), "g")
|
||||
var fixed = data.replace(variables, function(match) {
|
||||
return match.replace(/\d+$/, "")
|
||||
})
|
||||
return open + fixed + close
|
||||
})
|
||||
|
||||
//fix props
|
||||
var props = new RegExp("(\\.\\s*)(" + candidates + ")|([\\{,]\\s*)(" + candidates + ")(\\s*:)", "gm")
|
||||
code = code.replace(props, function(match, dot, a, pre, b, post) {
|
||||
if (dot) return dot + a.replace(/\d+$/, "")
|
||||
else return pre + b.replace(/\d+$/, "") + post
|
||||
})
|
||||
|
||||
return code
|
||||
.replace(/("|')use strict\1;?/gm, "") // remove extraneous "use strict"
|
||||
.replace(/module\.exports\s*=\s*/gm, rest ? "var _" + uuid + eq : def + (rest ? "_" : "") + variable + eq) // export
|
||||
+ (rest ? "\n" + def + variable + eq + "_" + uuid : "") // if `rest` is truthy, it means the expression is fluent or higher-order (e.g. require(path).foo or require(path)(foo)
|
||||
}
|
||||
|
||||
var versionTag = "bleeding-edge"
|
||||
var packageFile = __dirname + "/../package.json"
|
||||
var code = process(path.resolve(input), read(input))
|
||||
.replace(/^\s*((?:var|let|const|)[\t ]*)([\w_$\.]+)(\s*=\s*)(\2)(?=[\s]+(\w)|;|$)/gm, "") // remove assignments to self
|
||||
.replace(/;+(\r|\n|$)/g, ";$1") // remove redundant semicolons
|
||||
.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks
|
||||
.replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version
|
||||
|
||||
code = ";(function() {\n" + code + "\n}());"
|
||||
|
||||
if (!isFile(output) || code !== read(output)) {
|
||||
//try {new Function(code); console.log("build completed at " + new Date())} catch (e) {}
|
||||
error = null
|
||||
fs.writeFileSync(output, code, "utf8")
|
||||
else {
|
||||
// resolve as local dependency
|
||||
return path.resolve(path.dirname(filepath), filename + ".js")
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e.message)
|
||||
|
||||
var exportCode = function(filename, filepath, def, variable, eq, rest, uuid) {
|
||||
var code = read(filepath)
|
||||
// if there's a syntax error, report w/ proper stack trace
|
||||
try {new Function(code)} catch (e) {
|
||||
proc.exec("node " + filepath, function(e) {
|
||||
if (e !== null && e.message !== error) {
|
||||
error = e.message
|
||||
console.log("\x1b[31m" + e.message + "\x1b[0m")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// disambiguate collisions
|
||||
var ignored = {}
|
||||
code.replace(include, function(match, def, variable, eq, dep) {
|
||||
var filename = new Function("return " + dep).call()
|
||||
var binding = modules[resolve(filepath, filename)]
|
||||
if (binding != null) ignored[binding] = true
|
||||
})
|
||||
if (code.match(new RegExp("module\\.exports\\s*=\\s*" + variable + "\s*$", "m"))) ignored[variable] = true
|
||||
for (var binding in bindings) {
|
||||
if (!ignored[binding]) {
|
||||
var before = code
|
||||
code = code.replace(new RegExp("(\\b)" + binding + "\\b", "g"), binding + bindings[binding])
|
||||
if (before !== code) bindings[binding]++
|
||||
}
|
||||
}
|
||||
|
||||
// fix strings that got mangled by collision disambiguation
|
||||
var string = /(["'])((?:\\\1|.)*?)(\1)/g
|
||||
var candidates = Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|")
|
||||
code = code.replace(string, function(match, open, data, close) {
|
||||
var variables = new RegExp(Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|"), "g")
|
||||
var fixed = data.replace(variables, function(match) {
|
||||
return match.replace(/\d+$/, "")
|
||||
})
|
||||
return open + fixed + close
|
||||
})
|
||||
|
||||
//fix props
|
||||
var props = new RegExp("((?:[^:]\\/\\/.*)?\\.\\s*)(" + candidates + ")|([\\{,]\\s*)(" + candidates + ")(\\s*:)", "gm")
|
||||
code = code.replace(props, function(match, dot, a, pre, b, post) {
|
||||
if (dot && dot.indexOf("//") === 1) return match // Don't do anything because dot was matched in a comment
|
||||
else if (dot) return dot + a.replace(/\d+$/, "")
|
||||
else return pre + b.replace(/\d+$/, "") + post
|
||||
})
|
||||
|
||||
return code
|
||||
.replace(/("|')use strict\1;?/gm, "") // remove extraneous "use strict"
|
||||
.replace(/module\.exports\s*=\s*/gm, rest ? "var _" + uuid + eq : def + (rest ? "_" : "") + variable + eq) // export
|
||||
+ (rest ? "\n" + def + variable + eq + "_" + uuid : "") // if `rest` is truthy, it means the expression is fluent or higher-order (e.g. require(path).foo or require(path)(foo)
|
||||
}
|
||||
|
||||
var versionTag = "bleeding-edge"
|
||||
var packageFile = __dirname + "/../package.json"
|
||||
var code = process(path.resolve(input), read(input))
|
||||
.replace(/^\s*((?:var|let|const|)[\t ]*)([\w_$\.]+)(\s*=\s*)(\2)(?=[\s]+(\w)|;|$)/gm, "") // remove assignments to self
|
||||
.replace(/;+(\r|\n|$)/g, ";$1") // remove redundant semicolons
|
||||
.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks
|
||||
.replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version
|
||||
|
||||
code = ";(function() {\n" + code + "\n}());"
|
||||
|
||||
if (!isFile(output) || code !== read(output)) {
|
||||
//try {new Function(code); console.log("build completed at " + new Date())} catch (e) {}
|
||||
error = null
|
||||
fs.writeFileSync(output, code, "utf8")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ var fs = require("fs");
|
|||
var bundle = require("./bundle")
|
||||
var minify = require("./minify")
|
||||
|
||||
var aliases = {o: "output", m: "minify", w: "watch", a: "aggressive"}
|
||||
var aliases = {o: "output", m: "minify", w: "watch", a: "aggressive", s: "save"}
|
||||
var params = {}
|
||||
var args = process.argv.slice(2), command = null
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
|
|
@ -27,8 +27,6 @@ function add(value) {
|
|||
bundle(params.input, params.output, {watch: params.watch})
|
||||
if (params.minify) {
|
||||
minify(params.output, params.output, {watch: params.watch, advanced: params.aggressive}, function(stats) {
|
||||
var readme, kb;
|
||||
|
||||
function format(n) {
|
||||
return n.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
|
||||
}
|
||||
|
|
@ -36,14 +34,16 @@ if (params.minify) {
|
|||
console.log("Original size: " + format(stats.originalGzipSize) + " bytes gzipped (" + format(stats.originalSize) + " bytes uncompressed)")
|
||||
console.log("Compiled size: " + format(stats.compressedGzipSize) + " bytes gzipped (" + format(stats.compressedSize) + " bytes uncompressed)")
|
||||
|
||||
readme = fs.readFileSync("./README.md", "utf8")
|
||||
kb = stats.compressedGzipSize / 1024
|
||||
if (params.save) {
|
||||
var readme = fs.readFileSync("./README.md", "utf8")
|
||||
var kb = stats.compressedGzipSize / 1000
|
||||
|
||||
fs.writeFileSync("./README.md",
|
||||
readme.replace(
|
||||
/(<!-- size -->)(.+?)(<!-- \/size -->)/,
|
||||
"$1" + (kb % 1 ? kb.toFixed(2) : kb) + " KB$3"
|
||||
fs.writeFileSync("./README.md",
|
||||
readme.replace(
|
||||
/(<!-- size -->)(.+?)(<!-- \/size -->)/,
|
||||
"$1" + (kb % 1 ? kb.toFixed(2) : kb) + " KB$3"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ var bundle = require("../bundle")
|
|||
|
||||
var fs = require("fs")
|
||||
|
||||
var ns = "bundler/tests/"
|
||||
var ns = "./"
|
||||
function read(filepath) {
|
||||
try {return fs.readFileSync(ns + filepath, "utf8")} catch (e) {/* ignore */}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Mithril does not provide any animation APIs per se, since these other options ar
|
|||
|
||||
### Animation on element creation
|
||||
|
||||
Animating an element via CSS when the element created couldn't be simpler. Just add an animation to a CSS class normally:
|
||||
Animating an element via CSS when the element is created couldn't be simpler. Just add an animation to a CSS class normally:
|
||||
|
||||
```css
|
||||
.fancy {animation:fade-in 0.5s;}
|
||||
|
|
@ -41,7 +41,7 @@ m.mount(document.body, FancyComponent)
|
|||
|
||||
### Animation on element removal
|
||||
|
||||
The problem with animating before removing an element is that we must wait until the animation is complete before we can actually remove the element. Fortunately, Mithril offers a [`onbeforeremove`](lifecycle-methods.md#onbeforeremove) hook that allows us to defer the removal of an element.
|
||||
The problem with animating before removing an element is that we must wait until the animation is complete before we can actually remove the element. Fortunately, Mithril offers the [`onbeforeremove`](lifecycle-methods.md#onbeforeremove) hook that allows us to defer the removal of an element.
|
||||
|
||||
Let's create an `exit` animation that fades `opacity` from 1 to 0.
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ var FancyComponent = {
|
|||
onbeforeremove: function(vnode) {
|
||||
vnode.dom.classList.add("exit")
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, 500)
|
||||
vnode.dom.addEventListener("animationend", resolve)
|
||||
})
|
||||
},
|
||||
view: function() {
|
||||
|
|
@ -86,7 +86,7 @@ var FancyComponent = {
|
|||
|
||||
`vnode.dom` points to the root DOM element of the component (`<div class="fancy">`). We use the classList API here to add an `exit` class to `<div class="fancy">`.
|
||||
|
||||
Then we return a [Promise](promise.md) that resolves after half a second. When we return a promise from `onbeforeremove`, Mithril waits until the promise is resolved and only then it removes the element. In this case, it waits half a second, giving the exit animation the exact time it needs to complete.
|
||||
Then we return a [Promise](promise.md) that resolves when the `animationend` event fires. When we return a promise from `onbeforeremove`, Mithril waits until the promise is resolved and only then it removes the element. In this case, it waits for the exit animation to finish.
|
||||
|
||||
We can verify that both the enter and exit animations work by mounting the `Toggler` component:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Change log
|
||||
|
||||
- [v2.0.0-rc](#v200rc)
|
||||
- [v1.1.6](#v116)
|
||||
- [v1.1.5](#v115)
|
||||
- [v1.1.4](#v114)
|
||||
|
|
@ -10,6 +11,86 @@
|
|||
- [v1.0.1](#v101)
|
||||
- [Migrating from v0.2.x](#migrating-from-v02x)
|
||||
- [Older docs](http://mithril.js.org/archive/v0.2.5/index.html)
|
||||
- [ospec change-log](../ospec/change-log.md)
|
||||
|
||||
---
|
||||
|
||||
### v2.0.0-rc
|
||||
|
||||
#### Breaking changes
|
||||
|
||||
- API: Component vnode `children` are not normalized into vnodes on ingestion; normalization only happens if and when they are ingested by the view ([#2155](https://github.com/MithrilJS/mithril.js/pull/2155/) (thanks to [@magikstm](https://github.com/magikstm) for related optimization [#2064](https://github.com/MithrilJS/mithril.js/pull/2064)))
|
||||
- API: `m.redraw()` is always asynchronous ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
- API: `m.mount()` will only render its own root when called, it will not trigger a `redraw()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
- API: Assigning to `vnode.state` (as in `vnode.state = ...`) is no longer supported. Instead, an error is thrown if `vnode.state` changes upon the invocation of a lifecycle hook.
|
||||
- API: `m.request` will no longer reject the Promise on server errors (eg. status >= 400) if the caller supplies an `extract` callback. This gives applications more control over handling server responses.
|
||||
- hyperscript: when attributes have a `null` or `undefined` value, they are treated as if they were absent. [#1773](https://github.com/MithrilJS/mithril.js/issues/1773) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174))
|
||||
- hyperscript: when an attribute is defined on both the first and second argument (as a CSS selector and an `attrs` field, respectively), the latter takes precedence, except for `class` attributes that are still added together. [#2172](https://github.com/MithrilJS/mithril.js/issues/2172) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174))
|
||||
- stream: when a stream conditionally returns HALT, dependant stream will also end ([#2200](https://github.com/MithrilJS/mithril.js/pull/2200))
|
||||
- render: remove some redundancy within the component initialization code ([#2213](https://github.com/MithrilJS/mithril.js/pull/2213))
|
||||
- render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221))
|
||||
- render: simplify component removal ([#2214](https://github.com/MithrilJS/mithril.js/pull/2214))
|
||||
|
||||
#### News
|
||||
|
||||
- API: Introduction of `m.redraw.sync()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
- API: Event handlers may also be objects with `handleEvent` methods ([#1949](https://github.com/MithrilJS/mithril.js/pull/1949), [#2222](https://github.com/MithrilJS/mithril.js/pull/2222)).
|
||||
- API: `m.route.link` accepts an optional `options` object ([#1930](https://github.com/MithrilJS/mithril.js/pull/1930))
|
||||
- API: `m.request` better error message on JSON parse error - ([#2195](https://github.com/MithrilJS/mithril.js/pull/2195), [@codeclown](https://github.com/codeclown))
|
||||
- API: `m.request` supports `timeout` as attr - ([#1966](https://github.com/MithrilJS/mithril.js/pull/1966))
|
||||
- API: `m.request` supports `responseType` as attr - ([#2193](https://github.com/MithrilJS/mithril.js/pull/2193))
|
||||
- Mocks: add limited support for the DOMParser API ([#2097](https://github.com/MithrilJS/mithril.js/pull/2097))
|
||||
- API: add support for raw SVG in `m.trust()` string ([#2097](https://github.com/MithrilJS/mithril.js/pull/2097))
|
||||
- render/core: remove the DOM nodes recycling pool ([#2122](https://github.com/MithrilJS/mithril.js/pull/2122))
|
||||
- render/core: revamp the core diff engine, and introduce a longest-increasing-subsequence-based logic to minimize DOM operations when re-ordering keyed nodes.
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- API: `m.route.set()` causes all mount points to be redrawn ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
- render/attrs: Using style objects in hyperscript calls will now properly diff style properties from one render to another as opposed to re-writing all element style properties every render.
|
||||
- render/attrs All vnodes attributes are properly removed when absent or set to `null` or `undefined` [#1804](https://github.com/MithrilJS/mithril.js/issues/1804) [#2082](https://github.com/MithrilJS/mithril.js/issues/2082) ([#1865](https://github.com/MithrilJS/mithril.js/pull/1865), [#2130](https://github.com/MithrilJS/mithril.js/pull/2130))
|
||||
- render/core: Render state correctly on select change event [#1916](https://github.com/MithrilJS/mithril.js/issues/1916) ([#1918](https://github.com/MithrilJS/mithril.js/pull/1918) [@robinchew](https://github.com/robinchew), [#2052](https://github.com/MithrilJS/mithril.js/pull/2052))
|
||||
- render/core: fix various updateNodes/removeNodes issues when the pool and fragments are involved [#1990](https://github.com/MithrilJS/mithril.js/issues/1990), [#1991](https://github.com/MithrilJS/mithril.js/issues/1991), [#2003](https://github.com/MithrilJS/mithril.js/issues/2003), [#2021](https://github.com/MithrilJS/mithril.js/pull/2021)
|
||||
- render/core: fix crashes when the keyed vnodes with the same `key` had different `tag` values [#2128](https://github.com/MithrilJS/mithril.js/issues/2128) [@JacksonJN](https://github.com/JacksonJN) ([#2130](https://github.com/MithrilJS/mithril.js/pull/2130))
|
||||
- render/core: fix cached nodes behavior in some keyed diff scenarios [#2132](https://github.com/MithrilJS/mithril.js/issues/2132) ([#2130](https://github.com/MithrilJS/mithril.js/pull/2130))
|
||||
- render/events: `addEventListener` and `removeEventListener` are always used to manage event subscriptions, preventing external interference.
|
||||
- render/events: Event listeners allocate less memory, swap at low cost, and are properly diffed now when rendered via `m.mount()`/`m.redraw()`.
|
||||
- render/events: `Object.prototype` properties can no longer interfere with event listener calls.
|
||||
- render/events: Event handlers, when set to literally `undefined` (or any non-function), are now correctly removed.
|
||||
- render/hooks: fixed an ommission that caused `oninit` to be called unnecessarily in some cases [#1992](https://github.com/MithrilJS/mithril.js/issues/1992)
|
||||
- docs: tweaks: ([#2104](https://github.com/MithrilJS/mithril.js/pull/2104) [@mikeyb](https://github.com/mikeyb), [#2205](https://github.com/MithrilJS/mithril.js/pull/2205), [@cavemansspa](https://github.com/cavemansspa))
|
||||
- render/core: avoid touching `Object.prototype.__proto__` setter with `key: "__proto__"` in certain situations ([#2251](https://github.com/MithrilJS/mithril.js/pull/2251))
|
||||
|
||||
---
|
||||
|
||||
### v1.1.7
|
||||
|
||||
- Stream references no longer magically coerce to their underlying values ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150), breaking change: `mithril-stream@2.0.0`)
|
||||
- Promise polyfill implementation separated from polyfilling logic.
|
||||
- `PromisePolyfill` is now available on the exported/global `m`.
|
||||
|
||||
---
|
||||
|
||||
### v1.1.6
|
||||
|
||||
- core: render() function can no longer prevent from changing `document.activeElement` in lifecycle hooks ([#1988](https://github.com/MithrilJS/mithril.js/pull/1988), [@purplecode](https://github.com/purplecode))
|
||||
- core: don't call `onremove` on the children of components that return null from the view [#1921](https://github.com/MithrilJS/mithril.js/issues/1921) [@octavore](https://github.com/octavore) ([#1922](https://github.com/MithrilJS/mithril.js/pull/1922))
|
||||
- hypertext: correct handling of shared attributes object passed to `m()`. Will copy attributes when it's necessary [#1941](https://github.com/MithrilJS/mithril.js/issues/1941) [@s-ilya](https://github.com/s-ilya) ([#1942](https://github.com/MithrilJS/mithril.js/pull/1942))
|
||||
|
||||
|
||||
---
|
||||
|
||||
### v1.1.5
|
||||
|
||||
- API: If a user sets the Content-Type header within a request's options, that value will be the entire header value rather than being appended to the default value ([#1924](https://github.com/MithrilJS/mithril.js/pull/1924))
|
||||
|
||||
---
|
||||
|
||||
### v1.1.4
|
||||
|
||||
#### Bug fixes:
|
||||
|
||||
- Fix IE bug where active element is null causing render function to throw error ([#1943](https://github.com/MithrilJS/mithril.js/pull/1943), [@JacksonJN](https://github.com/JacksonJN))
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -75,10 +156,7 @@
|
|||
- router: Don't overwrite the options object when redirecting from `onmatch with m.route.set()` [#1857](https://github.com/MithrilJS/mithril.js/issues/1857) ([#1889](https://github.com/MithrilJS/mithril.js/pull/1889))
|
||||
- stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893))
|
||||
|
||||
#### Ospec improvements
|
||||
|
||||
- Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout))
|
||||
- Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529))
|
||||
---
|
||||
|
||||
#### Docs / Repo maintenance
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
Here are some examples of Mithril in action
|
||||
|
||||
- [Animation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html)
|
||||
- [Community Added Examples](https://how-to-mithril.js.org)
|
||||
- [DBMonster](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html)
|
||||
- [Markdown Editor](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/editor/index.html)
|
||||
- SVG: [Clock](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/tiger.html)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
- [How it works](#how-it-works)
|
||||
- [Flexibility](#flexibility)
|
||||
- [CSS selectors](#css-selectors)
|
||||
- [Attributes passed as the second argument](attributes-passed-as-the-second-argument)
|
||||
- [DOM attributes](#dom-attributes)
|
||||
- [Style attribute](#style-attribute)
|
||||
- [Events](#events)
|
||||
|
|
@ -144,7 +145,23 @@ m("a.link[href=/]", {
|
|||
// <a href="/" class="link selected">Home</a>
|
||||
```
|
||||
|
||||
If there are class names in both first and second arguments of `m()`, they are merged together as you would expect.
|
||||
### Attributes passed as the second argument
|
||||
|
||||
You can pass attributes, properties, events and lifecycle hooks in the second, optional argument (see the next sections for details).
|
||||
|
||||
```JS
|
||||
m("button", {
|
||||
class: "my-button",
|
||||
onclick: function() {/* ... */},
|
||||
oncreate: function() {/* ... */}
|
||||
})
|
||||
```
|
||||
|
||||
If the value of such an attribute is `null` or `undefined`, it is treated as if the attribute was absent.
|
||||
|
||||
If there are class names in both first and second arguments of `m()`, they are merged together as you would expect. If the value of the class in the second argument is `null`or `undefined`, it is ignored.
|
||||
|
||||
If another attribute is present in both the first and the second argument, the second one takes precedence even if it is is `null` or `undefined`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -161,6 +178,76 @@ m("input[readonly]")
|
|||
m("input[readOnly]")
|
||||
```
|
||||
|
||||
This even includes custom elements. For example, you can use [A-Frame](https://aframe.io/docs/0.8.0/introduction/) within Mithril, no problem!
|
||||
|
||||
```javascript
|
||||
m("a-scene", [
|
||||
m("a-box", {
|
||||
position: "-1 0.5 -3",
|
||||
rotation: "0 45 0",
|
||||
color: "#4CC3D9",
|
||||
}),
|
||||
|
||||
m("a-sphere", {
|
||||
position: "0 1.25 -5",
|
||||
radius: "1.25",
|
||||
color: "#EF2D5E",
|
||||
}),
|
||||
|
||||
m("a-cylinder", {
|
||||
position: "1 0.75 -3",
|
||||
radius: "0.5",
|
||||
height: "1.5",
|
||||
color: "#FFC65D",
|
||||
}),
|
||||
|
||||
m("a-plane", {
|
||||
position: "0 0 -4",
|
||||
rotation: "-90 0 0",
|
||||
width: "4",
|
||||
height: "4",
|
||||
color: "#7BC8A4",
|
||||
}),
|
||||
|
||||
m("a-sky", {
|
||||
color: "#ECECEC",
|
||||
}),
|
||||
])
|
||||
```
|
||||
|
||||
And yes, this translates to both attributes and properties, and it works just like they would in the DOM. Using [Brick's `brick-deck`](http://brick.mozilla.io/docs/brick-deck) as an example, they have a `selected-index` attribute with a corresponding `selectedIndex` getter/setter property.
|
||||
|
||||
```javascript
|
||||
m("brick-deck[selected-index=0]", [/* ... */]) // lowercase
|
||||
m("brick-deck[selectedIndex=0]", [/* ... */]) // uppercase
|
||||
// I know these look odd, but `brick-deck`'s `selectedIndex` property is a
|
||||
// string, not a number.
|
||||
m("brick-deck", {"selected-index": "0"}, [/* ... */])
|
||||
m("brick-deck", {"selectedIndex": "0"}, [/* ... */])
|
||||
```
|
||||
|
||||
For custom elements, it doesn't auto-stringify properties, in case they are objects, numbers, or some other non-string value. So assuming you had some custom element `my-special-element` that has an `elem.whitelist` array getter/setter property, you could do this, and it'd work as you'd expect:
|
||||
|
||||
```javascript
|
||||
m("my-special-element", {
|
||||
whitelist: [
|
||||
"https://example.com",
|
||||
"http://neverssl.com",
|
||||
"https://google.com",
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
If you have classes or IDs for those elements, the shorthands still work as you would expect. To pull another A-Frame example:
|
||||
|
||||
```javascript
|
||||
// These two are equivalent
|
||||
m("a-entity#player")
|
||||
m("a-entity", {id: "player"})
|
||||
```
|
||||
|
||||
Do note that all the properties with magic semantics, like lifecycle attributes, `onevent` handlers, `key`s, `class`, and `style`, those are still treated the same way they are for normal HTML elements.
|
||||
|
||||
---
|
||||
|
||||
### Style attribute
|
||||
|
|
@ -175,7 +262,7 @@ m("div[style=background:red]")
|
|||
|
||||
Using a string as a `style` would overwrite all inline styles in the element if it is redrawn, and not only CSS rules whose values have changed.
|
||||
|
||||
Mithril does not attempt to add units to number values.
|
||||
Mithril does not attempt to add units to number values. It simply stringifies them.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Let's create an HTML file to follow along:
|
|||
|
||||
```markup
|
||||
<body>
|
||||
<script src="//unpkg.com/mithril/mithril.js"></script>
|
||||
<script src="https://unpkg.com/mithril/mithril.js"></script>
|
||||
<script>
|
||||
var root = document.body
|
||||
|
||||
|
|
|
|||
10
docs/lint.js
10
docs/lint.js
|
|
@ -101,12 +101,14 @@ function ensureLinkIsValid(file, data) {
|
|||
}
|
||||
|
||||
function initMocks() {
|
||||
global.window = require("../test-utils/browserMock")() // eslint-disable-line global-require
|
||||
/* eslint-disable global-require */
|
||||
global.window = require("../test-utils/browserMock")()
|
||||
global.document = window.document
|
||||
global.m = require("../index") // eslint-disable-line global-require
|
||||
global.o = require("../ospec/ospec") // eslint-disable-line global-require
|
||||
global.stream = require("../stream") // eslint-disable-line global-require
|
||||
global.m = require("../index")
|
||||
global.o = require("../ospec/ospec")
|
||||
global.stream = require("../stream")
|
||||
global.alert = function() {}
|
||||
/* eslint-enable global-require */
|
||||
|
||||
//routes consumed by request.md
|
||||
global.window.$defineRoutes({
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ A [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/G
|
|||
|
||||
A Promise is a mechanism for working with asynchronous computations.
|
||||
|
||||
Mithril provides a polyfill when the environment does not support Promises. The polyfill can also be referenced specifically via `m.PromisePolyfill`.
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [Static members](#static-members)
|
||||
-[m.redraw.sync()](#mredrawsync)
|
||||
- [How it works](#how-it-works)
|
||||
|
||||
---
|
||||
|
|
@ -14,8 +16,6 @@ You DON'T need to call it if data is modified within the execution context of an
|
|||
|
||||
You DO need to call it in `setTimeout`/`setInterval`/`requestAnimationFrame` callbacks, or callbacks from 3rd party libraries.
|
||||
|
||||
Typically, `m.redraw` triggers an asynchronous redraws, but it may trigger synchronously if Mithril detects it's possible to improve performance by doing so (i.e. if no redraw was requested within the last animation frame). You should write code assuming that it always redraws asynchronously.
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
|
@ -26,6 +26,16 @@ Argument | Type | Required | Description
|
|||
----------- | -------------------- | -------- | ---
|
||||
**returns** | | | Returns nothing
|
||||
|
||||
#### Static members
|
||||
|
||||
##### m.redraw.sync
|
||||
|
||||
`m.redraw.sync()`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
**returns** | | | Returns nothing
|
||||
|
||||
---
|
||||
|
||||
### How it works
|
||||
|
|
@ -34,4 +44,12 @@ When callbacks outside of Mithril run, you need to notify Mithril's rendering en
|
|||
|
||||
To trigger a redraw, call `m.redraw()`. Note that `m.redraw` only works if you used `m.mount` or `m.route`. If you rendered via `m.render`, you should use `m.render` to redraw.
|
||||
|
||||
You should not call m.redraw from a [lifecycle method](lifecycle-methods.md). Doing so will result in undefined behavior.
|
||||
`m.redraw()` always triggers an asynchronous redraws, whereas `m.redraw.sync()` triggers a synchronous one. `m.redraw()` is tied to `window.requestAnimationFrame()` (we provide a fallback for IE9). It will thus typically fire at most 60 times per second. It may fire faster if your monitor has a higher refresh rate.
|
||||
|
||||
`m.redraw.sync()` is mostly intended to make videos play work in iOS. That only works in response to user-triggered events. It comes with several caveat:
|
||||
|
||||
- You should not call `m.redraw.sync()` from a [lifecycle method](lifecycle-methods.md) or the `view()` method of a component. Doing so will result in undefined behavior (it throws an error when possible).
|
||||
- `m.redraw.sync()` called from an event handler can cause the DOM to be modified while an event is bubbling. Depending on the structure of the old and new DOM trees, the event can finish the bubbling phase in the new tree and trigger unwanted handlers.
|
||||
- It is not throttled. One call to `m.redraw.sync()` causes immediately one `m.render()` call per root registered with [`m.mount()`](mount.md) or [`m.route()`](route.md).
|
||||
|
||||
`m.redraw()` doesn't have any of those issues, you can call it from wherever you like.
|
||||
|
|
@ -50,6 +50,7 @@ Argument | Type | Required | Descr
|
|||
`options.password` | `String` | No | A password for HTTP authorization. Defaults to `undefined`. This option is provided for `XMLHttpRequest` compatibility, but you should avoid using it because it sends the password in plain text over the network.
|
||||
`options.withCredentials` | `Boolean` | No | Whether to send cookies to 3rd party domains. Defaults to `false`
|
||||
`options.timeout` | `Number` | No | The amount of milliseconds a request can take before automatically being [terminated](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout). Defaults to `undefined`.
|
||||
`options.responseType` | `String` | No | The expected [type](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType) of the response. Defaults to `undefined`.
|
||||
`options.config` | `xhr = Function(xhr)` | No | Exposes the underlying XMLHttpRequest object for low-level configuration. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
|
||||
`options.headers` | `Object` | No | Headers to append to the request before sending it (applied right before `options.config`).
|
||||
`options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ Now we can style the `UserList` component:
|
|||
|
||||
The CSS above is written using a convention of keeping all styles for a rule in a single line, in alphabetical order. This convention is designed to take maximum advantage of screen real estate, and makes it easier to scan the CSS selectors (since they are always on the left side) and their logical grouping, and it enforces predictable and uniform placement of CSS rules for each selector.
|
||||
|
||||
Obviously you can use whatever spacing/indentation convention you prefer. The example above is just an illustration of a not-so-widespread convention that has strong rationales behind it, but deviate from the more widespread cosmetic-oriented spacing conventions.
|
||||
Obviously you can use whatever spacing/indentation convention you prefer. The example above is just an illustration of a not-so-widespread convention that has strong rationales behind it, but deviates from the more widespread cosmetic-oriented spacing conventions.
|
||||
|
||||
Reloading the browser window now should display some styled elements.
|
||||
|
||||
|
|
|
|||
|
|
@ -120,11 +120,13 @@ Argument | Type | Required | Description
|
|||
|
||||
Creates a new stream with the results of calling the function on every value in the stream with an accumulator and the incoming value.
|
||||
|
||||
Note that you can prevent dependent streams from being updated by returning the special value `stream.HALT` inside the accumulator function.
|
||||
|
||||
`stream = Stream.scan(fn, accumulator, stream)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------- | -------------------------------- | -------- | ---
|
||||
`fn` | `(accumulator, value) -> result` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value
|
||||
`fn` | `(accumulator, value) -> result \| HALT` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value
|
||||
`accumulator` | `any` | Yes | The starting value for the accumulator
|
||||
`stream` | `Stream` | Yes | Stream containing the values
|
||||
**returns** | `Stream` | | Returns a new stream containing the result
|
||||
|
|
@ -502,13 +504,6 @@ var serialized = JSON.stringify(value)
|
|||
console.log(serialized) // logs 123
|
||||
```
|
||||
|
||||
Streams also implement a `valueOf` method that returns the value of the stream.
|
||||
|
||||
```javascript
|
||||
var value = stream(123)
|
||||
console.log("test " + value) // logs "test 123"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Streams do not trigger rendering
|
||||
|
|
|
|||
1
index.js
1
index.js
|
|
@ -17,5 +17,6 @@ m.parseQueryString = require("./querystring/parse")
|
|||
m.buildQueryString = require("./querystring/build")
|
||||
m.version = "bleeding-edge"
|
||||
m.vnode = require("./render/vnode")
|
||||
m.PromisePolyfill = require("./promise/polyfill")
|
||||
|
||||
module.exports = m
|
||||
|
|
|
|||
940
mithril.js
940
mithril.js
File diff suppressed because it is too large
Load diff
92
mithril.min.js
vendored
92
mithril.min.js
vendored
|
|
@ -1,44 +1,48 @@
|
|||
(function(){function B(b,d,f,g,e,n){return{tag:b,key:d,attrs:f,children:g,text:e,dom:n,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function N(b){for(var d in b)if(G.call(b,d))return!1;return!0}function D(b){var d=arguments[1],f=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b){var g;if(!(g=O[b])){var e="div";for(var n=[],h={};g=Q.exec(b);){var q=
|
||||
g[1],m=g[2];""===q&&""!==m?e=m:"#"===q?h.id=m:"."===q?n.push(m):"["===g[3][0]&&((q=g[6])&&(q=q.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===g[4]?n.push(q):h[g[4]]=""===q?q:q||!0)}0<n.length&&(h.className=n.join(" "));g=O[b]={tag:e,attrs:h}}}if(null==d)d={};else if("object"!==typeof d||null!=d.tag||Array.isArray(d))d={},f=1;if(arguments.length===f+1)e=arguments[f],Array.isArray(e)||(e=[e]);else for(e=[];f<arguments.length;)e.push(arguments[f++]);f=B.normalizeChildren(e);if("string"===
|
||||
typeof b){e=!1;var k,t;n=d.className||d["class"];if(!N(g.attrs)&&!N(d)){h={};for(var a in d)G.call(d,a)&&(h[a]=d[a]);d=h}for(a in g.attrs)G.call(g.attrs,a)&&(d[a]=g.attrs[a]);void 0!==n&&(void 0!==d["class"]&&(d["class"]=void 0,d.className=n),null!=g.attrs.className&&(d.className=g.attrs.className+" "+n));for(a in d)if(G.call(d,a)&&"key"!==a){e=!0;break}Array.isArray(f)&&1===f.length&&null!=f[0]&&"#"===f[0].tag?t=f[0].children:k=f;return B(g.tag,d.key,e?d:void 0,k,t)}return B(b,d.key,d,f)}function R(b){var d=
|
||||
0,f=null,g="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var e=Date.now();0===d||16<=e-d?(d=e,b()):null===f&&(f=g(function(){f=null;b();d=Date.now()},16-(e-d)))}}B.normalize=function(b){return Array.isArray(b)?B("[",void 0,void 0,B.normalizeChildren(b),void 0,void 0):null!=b&&"object"!==typeof b?B("#",void 0,void 0,!1===b?"":b,void 0,void 0):b};B.normalizeChildren=function(b){for(var d=0;d<b.length;d++)b[d]=B.normalize(b[d]);return b};var Q=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,
|
||||
O={},G={}.hasOwnProperty;D.trust=function(b){null==b&&(b="");return B("<",void 0,void 0,b,void 0,void 0)};D.fragment=function(b,d){return B("[",b.key,b,B.normalizeChildren(d),void 0,void 0)};var x=function(b){function d(b,a){return function E(d){var h;try{if(!a||null==d||"object"!==typeof d&&"function"!==typeof d||"function"!==typeof(h=d.then))k(function(){a||0!==b.length||console.error("Possible unhandled promise rejection:",d);for(var f=0;f<b.length;f++)b[f](d);e.length=0;n.length=0;m.state=a;m.retry=
|
||||
function(){E(d)}});else{if(d===g)throw new TypeError("Promise can't be resolved w/ itself");f(h.bind(d))}}catch(S){q(S)}}}function f(b){function a(a){return function(b){0<d++||a(b)}}var d=0,f=a(q);try{b(a(h),f)}catch(E){f(E)}}if(!(this instanceof x))throw Error("Promise must be called with `new`");if("function"!==typeof b)throw new TypeError("executor must be a function");var g=this,e=[],n=[],h=d(e,!0),q=d(n,!1),m=g._instance={resolvers:e,rejectors:n},k="function"===typeof setImmediate?setImmediate:
|
||||
setTimeout;f(b)};x.prototype.then=function(b,d){function f(b,d,f,h){d.push(function(a){if("function"!==typeof b)f(a);else try{e(b(a))}catch(w){n&&n(w)}});"function"===typeof g.retry&&h===g.state&&g.retry()}var g=this._instance,e,n,h=new x(function(b,d){e=b;n=d});f(b,g.resolvers,e,!0);f(d,g.rejectors,n,!1);return h};x.prototype["catch"]=function(b){return this.then(null,b)};x.resolve=function(b){return b instanceof x?b:new x(function(d){d(b)})};x.reject=function(b){return new x(function(d,f){f(b)})};
|
||||
x.all=function(b){return new x(function(d,f){var g=b.length,e=0,n=[];if(0===b.length)d([]);else for(var h=0;h<b.length;h++)(function(h){function m(b){e++;n[h]=b;e===g&&d(n)}null==b[h]||"object"!==typeof b[h]&&"function"!==typeof b[h]||"function"!==typeof b[h].then?m(b[h]):b[h].then(m,f)})(h)})};x.race=function(b){return new x(function(d,f){for(var g=0;g<b.length;g++)b[g].then(d,f)})};"undefined"!==typeof window?("undefined"===typeof window.Promise&&(window.Promise=x),x=window.Promise):"undefined"!==
|
||||
typeof global&&("undefined"===typeof global.Promise&&(global.Promise=x),x=global.Promise);var F=function(b){function d(b,g){if(Array.isArray(g))for(var e=0;e<g.length;e++)d(b+"["+e+"]",g[e]);else if("[object Object]"===Object.prototype.toString.call(g))for(e in g)d(b+"["+e+"]",g[e]);else f.push(encodeURIComponent(b)+(null!=g&&""!==g?"="+encodeURIComponent(g):""))}if("[object Object]"!==Object.prototype.toString.call(b))return"";var f=[],g;for(g in b)d(g,b[g]);return f.join("&")},T=/^file:\/\//i,L=
|
||||
function(b,d){function f(){function a(){0===--b&&"function"===typeof t&&t()}var b=0;return function u(d){var f=d.then;d.then=function(){b++;var e=f.apply(d,arguments);e.then(a,function(d){a();if(0===b)throw d;});return u(e)};return d}}function g(a,b){if("string"===typeof a){var d=a;a=b||{};null==a.url&&(a.url=d)}return a}function e(a,b){if(null==b)return a;for(var d=a.match(/:[^\/]+/gi)||[],f=0;f<d.length;f++){var e=d[f].slice(1);null!=b[e]&&(a=a.replace(d[f],b[e]))}return a}function n(a,b){var d=
|
||||
F(b);if(""!==d){var f=0>a.indexOf("?")?"?":"&";a+=f+d}return a}function h(a){try{return""!==a?JSON.parse(a):null}catch(w){throw Error(a);}}function q(a){return a.responseText}function m(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;d<b.length;d++)b[d]=new a(b[d]);else return new a(b);return b}var k=0,t;return{request:function(a,k){var t=f();a=g(a,k);var w=new d(function(d,f){null==a.method&&(a.method="GET");a.method=a.method.toUpperCase();var g="GET"===a.method||"TRACE"===a.method?
|
||||
!1:"boolean"===typeof a.useBody?a.useBody:!0;"function"!==typeof a.serialize&&(a.serialize="undefined"!==typeof FormData&&a.data instanceof FormData?function(a){return a}:JSON.stringify);"function"!==typeof a.deserialize&&(a.deserialize=h);"function"!==typeof a.extract&&(a.extract=q);a.url=e(a.url,a.data);g?a.data=a.serialize(a.data):a.url=n(a.url,a.data);var k=new b.XMLHttpRequest,t=!1,w=k.abort;k.abort=function(){t=!0;w.call(k)};k.open(a.method,a.url,"boolean"===typeof a.async?a.async:!0,"string"===
|
||||
typeof a.user?a.user:void 0,"string"===typeof a.password?a.password:void 0);a.serialize!==JSON.stringify||!g||a.headers&&a.headers.hasOwnProperty("Content-Type")||k.setRequestHeader("Content-Type","application/json; charset=utf-8");a.deserialize!==h||a.headers&&a.headers.hasOwnProperty("Accept")||k.setRequestHeader("Accept","application/json, text/*");a.withCredentials&&(k.withCredentials=a.withCredentials);for(var u in a.headers)({}).hasOwnProperty.call(a.headers,u)&&k.setRequestHeader(u,a.headers[u]);
|
||||
"function"===typeof a.config&&(k=a.config(k,a)||k);k.onreadystatechange=function(){if(!t&&4===k.readyState)try{var b=a.extract!==q?a.extract(k,a):a.deserialize(a.extract(k,a));if(200<=k.status&&300>k.status||304===k.status||T.test(a.url))d(m(a.type,b));else{var l=Error(k.responseText),c;for(c in b)l[c]=b[c];f(l)}}catch(p){f(p)}};g&&null!=a.data?k.send(a.data):k.send()});return!0===a.background?w:t(w)},jsonp:function(a,h){var t=f();a=g(a,h);var q=new d(function(d,f){var g=a.callbackName||"_mithril_"+
|
||||
Math.round(1E16*Math.random())+"_"+k++,h=b.document.createElement("script");b[g]=function(f){h.parentNode.removeChild(h);d(m(a.type,f));delete b[g]};h.onerror=function(){h.parentNode.removeChild(h);f(Error("JSONP request failed"));delete b[g]};null==a.data&&(a.data={});a.url=e(a.url,a.data);a.data[a.callbackKey||"callback"]=g;h.src=n(a.url,a.data);b.document.documentElement.appendChild(h)});return!0===a.background?q:t(q)},setCompletionCallback:function(a){t=a}}}(window,x),P=function(b){function d(l,
|
||||
c,p,a,b,d,g){for(;p<a;p++){var v=c[p];null!=v&&f(l,v,b,g,d)}}function f(l,c,p,a,b){var v=c.tag;if("string"===typeof v)switch(c.state={},null!=c.attrs&&D(c.attrs,c,p),v){case "#":return c.dom=A.createTextNode(c.children),k(l,c.dom,b),c.dom;case "<":return g(l,c,b);case "[":var h=A.createDocumentFragment();null!=c.children&&(v=c.children,d(h,v,0,v.length,p,null,a));c.dom=h.firstChild;c.domSize=h.childNodes.length;k(l,h,b);return h;default:var m=c.tag,r=(v=c.attrs)&&v.is;m=(a=c.attrs&&c.attrs.xmlns||
|
||||
G[c.tag]||a)?r?A.createElementNS(a,m,{is:r}):A.createElementNS(a,m):r?A.createElement(m,{is:r}):A.createElement(m);c.dom=m;if(null!=v)for(h in r=a,v)E(c,h,null,v[h],r);k(l,m,b);null!=c.attrs&&null!=c.attrs.contenteditable?t(c):(null!=c.text&&(""!==c.text?m.textContent=c.text:c.children=[B("#",void 0,void 0,c.text,void 0,void 0)]),null!=c.children&&(l=c.children,d(m,l,0,l.length,p,null,a),l=c.attrs,"select"===c.tag&&null!=l&&("value"in l&&E(c,"value",null,l.value,void 0),"selectedIndex"in l&&E(c,"selectedIndex",
|
||||
null,l.selectedIndex,void 0))));return m}else return e(c,p),null!=c.instance?(p=f(l,c.instance,p,a,b),c.dom=c.instance.dom,c.domSize=null!=c.dom?c.instance.domSize:0,k(l,p,b),c=p):(c.domSize=0,c=K),c}function g(l,c,a){var p={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(c.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div";p=A.createElement(p);p.innerHTML=c.children;c.dom=p.firstChild;c.domSize=p.childNodes.length;c=A.createDocumentFragment();
|
||||
for(var b;b=p.firstChild;)c.appendChild(b);k(l,c,a);return c}function e(l,c){if("function"===typeof l.tag.view){l.state=Object.create(l.tag);var a=l.state.view;if(null!=a.$$reentrantLock$$)return K;a.$$reentrantLock$$=!0}else{l.state=void 0;a=l.tag;if(null!=a.$$reentrantLock$$)return K;a.$$reentrantLock$$=!0;l.state=null!=l.tag.prototype&&"function"===typeof l.tag.prototype.view?new l.tag(l):l.tag(l)}l._state=l.state;null!=l.attrs&&D(l.attrs,l,c);D(l._state,l,c);l.instance=B.normalize(l._state.view.call(l.state,
|
||||
l));if(l.instance===l)throw Error("A view cannot return the vnode it received as argument");a.$$reentrantLock$$=null}function n(l,c,p,b,g,e,n){if(c!==p&&(null!=c||null!=p))if(null==c)d(l,p,0,p.length,g,e,n);else if(null==p)a(c,0,c.length,p);else{if(c.length===p.length){for(var v=!1,r=0;r<p.length;r++)if(null!=p[r]&&null!=c[r]){v=null==p[r].key&&null==c[r].key;break}if(v){for(r=0;r<c.length;r++)c[r]!==p[r]&&(null==c[r]&&null!=p[r]?f(l,p[r],g,n,m(c,r+1,e)):null==p[r]?a(c,r,r+1,p):h(l,c[r],p[r],g,m(c,
|
||||
r+1,e),b,n));return}}if(!b)a:{if(null!=c.pool&&Math.abs(c.pool.length-p.length)<=Math.abs(c.length-p.length)&&(b=p[0]&&p[0].children&&p[0].children.length||0,Math.abs((c.pool[0]&&c.pool[0].children&&c.pool[0].children.length||0)-b)<=Math.abs((c[0]&&c[0].children&&c[0].children.length||0)-b))){b=!0;break a}b=!1}if(b){var t=c.pool;c=c.concat(c.pool)}r=v=0;for(var w=c.length-1,y=p.length-1,H;w>=v&&y>=r;){var u=c[v],z=p[r];if(u!==z||b)if(null==u)v++;else if(null==z)r++;else if(u.key===z.key){var C=null!=
|
||||
t&&v>=c.length-t.length||null==t&&b;v++;r++;h(l,u,z,g,m(c,v,e),C,n);b&&u.tag===z.tag&&k(l,q(u),e)}else if(u=c[w],u!==z||b)if(null==u)w--;else if(null==z)r++;else if(u.key===z.key)C=null!=t&&w>=c.length-t.length||null==t&&b,h(l,u,z,g,m(c,w+1,e),C,n),(b||r<y)&&k(l,q(u),m(c,v,e)),w--,r++;else break;else w--,r++;else v++,r++}for(;w>=v&&y>=r;){u=c[w];z=p[y];if(u!==z||b)if(null==u)w--;else{if(null!=z)if(u.key===z.key)C=null!=t&&w>=c.length-t.length||null==t&&b,h(l,u,z,g,m(c,w+1,e),C,n),b&&u.tag===z.tag&&
|
||||
k(l,q(u),e),null!=u.dom&&(e=u.dom),w--;else{if(!H){H=c;u=w;C={};var A;for(A=0;A<u;A++){var x=H[A];null!=x&&(x=x.key,null!=x&&(C[x]=A))}H=C}null!=z&&(u=H[z.key],null!=u?(C=c[u],h(l,C,z,g,m(c,w+1,e),b,n),k(l,q(C),e),c[u].skip=!0,null!=C.dom&&(e=C.dom)):e=f(l,z,g,n,e))}y--}else w--,y--;if(y<r)break}d(l,p,r,y+1,g,e,n);a(c,v,w+1,p)}}function h(l,c,a,b,d,m,k){var p=c.tag;if(p===a.tag){a.state=c.state;a._state=c._state;a.events=c.events;var v;if(v=!m){var C,z;null!=a.attrs&&"function"===typeof a.attrs.onbeforeupdate&&
|
||||
(C=a.attrs.onbeforeupdate.call(a.state,a,c));"string"!==typeof a.tag&&"function"===typeof a._state.onbeforeupdate&&(z=a._state.onbeforeupdate.call(a.state,a,c));void 0===C&&void 0===z||C||z?v=!1:(a.dom=c.dom,a.domSize=c.domSize,a.instance=c.instance,v=!0)}if(!v)if("string"===typeof p)switch(null!=a.attrs&&(m?(a.state={},D(a.attrs,a,b)):J(a.attrs,a,b)),p){case "#":c.children.toString()!==a.children.toString()&&(c.dom.nodeValue=a.children);a.dom=c.dom;break;case "<":c.children!==a.children?(q(c),g(l,
|
||||
a,d)):(a.dom=c.dom,a.domSize=c.domSize);break;case "[":n(l,c.children,a.children,m,b,d,k);c=0;b=a.children;a.dom=null;if(null!=b){for(m=0;m<b.length;m++){var y=b[m];null!=y&&null!=y.dom&&(null==a.dom&&(a.dom=y.dom),c+=y.domSize||1)}1!==c&&(a.domSize=c)}break;default:l=a.dom=c.dom;k=a.attrs&&a.attrs.xmlns||G[a.tag]||k;"textarea"===a.tag&&(null==a.attrs&&(a.attrs={}),null!=a.text&&(a.attrs.value=a.text,a.text=void 0));d=c.attrs;p=a.attrs;v=k;if(null!=p)for(y in p)E(a,y,d&&d[y],p[y],v);if(null!=d)for(y in d)null!=
|
||||
p&&y in p||("className"===y&&(y="class"),"o"!==y[0]||"n"!==y[1]||u(y)?"key"!==y&&a.dom.removeAttribute(y):x(a,y,void 0));null!=a.attrs&&null!=a.attrs.contenteditable?t(a):null!=c.text&&null!=a.text&&""!==a.text?c.text.toString()!==a.text.toString()&&(c.dom.firstChild.nodeValue=a.text):(null!=c.text&&(c.children=[B("#",void 0,void 0,c.text,void 0,c.dom.firstChild)]),null!=a.text&&(a.children=[B("#",void 0,void 0,a.text,void 0,void 0)]),n(l,c.children,a.children,m,b,null,k))}else{if(m)e(a,b);else{a.instance=
|
||||
B.normalize(a._state.view.call(a.state,a));if(a.instance===a)throw Error("A view cannot return the vnode it received as argument");null!=a.attrs&&J(a.attrs,a,b);J(a._state,a,b)}null!=a.instance?(null==c.instance?f(l,a.instance,b,k,d):h(l,c.instance,a.instance,b,d,m,k),a.dom=a.instance.dom,a.domSize=a.instance.domSize):null!=c.instance?(w(c.instance,null),a.dom=void 0,a.domSize=0):(a.dom=c.dom,a.domSize=c.domSize)}}else w(c,null),f(l,a,b,k,d)}function q(a){var c=a.domSize;if(null!=c||null==a.dom){var b=
|
||||
A.createDocumentFragment();if(0<c){for(a=a.dom;--c;)b.appendChild(a.nextSibling);b.insertBefore(a,b.firstChild)}return b}return a.dom}function m(a,c,b){for(;c<a.length;c++)if(null!=a[c]&&null!=a[c].dom)return a[c].dom;return b}function k(a,c,b){b&&b.parentNode?a.insertBefore(c,b):a.appendChild(c)}function t(a){var c=a.children;if(null!=c&&1===c.length&&"<"===c[0].tag)c=c[0].children,a.dom.innerHTML!==c&&(a.dom.innerHTML=c);else if(null!=a.text||null!=c&&0!==c.length)throw Error("Child node of a contenteditable must be trusted");
|
||||
}function a(a,c,b,d){for(;c<b;c++){var l=a[c];null!=l&&(l.skip?l.skip=!1:w(l,d))}}function w(a,c){function b(){if(++d===l&&(C(a),a.dom)){var b=a.domSize||1;if(1<b)for(var e=a.dom;--b;){var g=e.nextSibling,f=g.parentNode;null!=f&&f.removeChild(g)}b=a.dom;e=b.parentNode;null!=e&&e.removeChild(b);if(b=null!=c&&null==a.domSize)b=a.attrs,b=!(null!=b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(c.pool?c.pool.push(a):c.pool=[a])}}var l=1,d=0;if(a.attrs&&"function"===
|
||||
typeof a.attrs.onbeforeremove){var e=a.attrs.onbeforeremove.call(a.state,a);null!=e&&"function"===typeof e.then&&(l++,e.then(b,b))}"string"!==typeof a.tag&&"function"===typeof a._state.onbeforeremove&&(e=a._state.onbeforeremove.call(a.state,a),null!=e&&"function"===typeof e.then&&(l++,e.then(b,b)));b()}function C(a){a.attrs&&"function"===typeof a.attrs.onremove&&a.attrs.onremove.call(a.state,a);if("string"!==typeof a.tag)"function"===typeof a._state.onremove&&a._state.onremove.call(a.state,a),null!=
|
||||
a.instance&&C(a.instance);else if(a=a.children,Array.isArray(a))for(var c=0;c<a.length;c++){var b=a[c];null!=b&&C(b)}}function E(a,b,d,e,g){var c=a.dom;if("key"!==b&&"is"!==b&&(d!==e||"value"===b||"checked"===b||"selectedIndex"===b||"selected"===b&&a.dom===A.activeElement||"object"===typeof e)&&"undefined"!==typeof e&&!u(b)){var l=b.indexOf(":");if(-1<l&&"xlink"===b.substr(0,l))c.setAttributeNS("http://www.w3.org/1999/xlink",b.slice(l+1),e);else if("o"===b[0]&&"n"===b[1]&&"function"===typeof e)x(a,
|
||||
b,e);else if("style"===b)if(a=d,a===e&&(c.style.cssText="",a=null),null==e)c.style.cssText="";else if("string"===typeof e)c.style.cssText=e;else{"string"===typeof a&&(c.style.cssText="");for(var f in e)c.style[f]=e[f];if(null!=a&&"string"!==typeof a)for(f in a)f in e||(c.style[f]="")}else if(b in c&&"href"!==b&&"list"!==b&&"form"!==b&&"width"!==b&&"height"!==b&&void 0===g&&!(a.attrs.is||-1<a.tag.indexOf("-"))){if("value"===b){f=""+e;if(("input"===a.tag||"textarea"===a.tag)&&a.dom.value===f&&a.dom===
|
||||
A.activeElement)return;if("select"===a.tag)if(null===e){if(-1===a.dom.selectedIndex&&a.dom===A.activeElement)return}else if(null!==d&&a.dom.value===f&&a.dom===A.activeElement)return;if("option"===a.tag&&null!=d&&a.dom.value===f)return}"input"===a.tag&&"type"===b?c.setAttribute(b,e):c[b]=e}else"boolean"===typeof e?e?c.setAttribute(b,""):c.removeAttribute(b):c.setAttribute("className"===b?"class":b,e)}}function u(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===
|
||||
a||"onbeforeupdate"===a}function x(a,b,d){var c=a.dom,e="function"!==typeof F?d:function(a){var b=d.call(c,a);F.call(c,a);return b};if(b in c)c[b]="function"===typeof d?e:null;else{var f=b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==e&&(null!=a.events[b]&&c.removeEventListener(f,a.events[b],!1),"function"===typeof d&&(a.events[b]=e,c.addEventListener(f,a.events[b],!1)))}}function D(a,b,d){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&d.push(a.oncreate.bind(b.state,
|
||||
b))}function J(a,b,d){"function"===typeof a.onupdate&&d.push(a.onupdate.bind(b.state,b))}var A=b.document,K=A.createDocumentFragment(),G={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"},F;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],d=A.activeElement,e=a.namespaceURI;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);n(a,a.vnodes,B.normalizeChildren(b),!1,c,null,"http://www.w3.org/1999/xhtml"===
|
||||
e?void 0:e);a.vnodes=b;null!=d&&A.activeElement!==d&&d.focus();for(d=0;d<c.length;d++)c[d]()},setEventCallback:function(a){return F=a}}},I=function(b){function d(b){b=g.indexOf(b);-1<b&&g.splice(b,2)}function f(){for(var b=1;b<g.length;b+=2)g[b]()}b=P(b);b.setEventCallback(function(b){!1===b.redraw?b.redraw=void 0:f()});var g=[];return{subscribe:function(b,f){d(b);g.push(b,R(f))},unsubscribe:d,redraw:f,render:b.render}}(window);L.setCompletionCallback(I.redraw);D.mount=function(b){return function(d,
|
||||
f){if(null===f)b.render(d,[]),b.unsubscribe(d);else{if(null==f.view&&"function"!==typeof f)throw Error("m.mount(element, component) expects a component, not a vnode");b.subscribe(d,function(){b.render(d,B(f))});b.redraw()}}}(I);var U=x,M=function(b){if(""===b||null==b)return{};"?"===b.charAt(0)&&(b=b.slice(1));b=b.split("&");for(var d={},f={},g=0;g<b.length;g++){var e=b[g].split("="),n=decodeURIComponent(e[0]);e=2===e.length?decodeURIComponent(e[1]):"";"true"===e?e=!0:"false"===e&&(e=!1);var h=n.split(/\]\[?|\[/),
|
||||
q=d;-1<n.indexOf("[")&&h.pop();for(var m=0;m<h.length;m++){n=h[m];var k=h[m+1];k=""==k||!isNaN(parseInt(k,10));var t=m===h.length-1;""===n&&(n=h.slice(0,m).join(),null==f[n]&&(f[n]=0),n=f[n]++);null==q[n]&&(q[n]=t?e:k?[]:{});q=q[n]}}return d},V=function(b){function d(d){var e=b.location[d].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===d&&"/"!==e[0]&&(e="/"+e);return e}function f(b){return function(){null==h&&(h=n(function(){h=null;b()}))}}function g(b,d,e){var a=b.indexOf("?"),
|
||||
f=b.indexOf("#"),g=-1<a?a:-1<f?f:b.length;if(-1<a){a=M(b.slice(a+1,-1<f?f:b.length));for(var h in a)d[h]=a[h]}if(-1<f)for(h in d=M(b.slice(f+1)),d)e[h]=d[h];return b.slice(0,g)}var e="function"===typeof b.history.pushState,n="function"===typeof setImmediate?setImmediate:setTimeout,h,q={prefix:"#!",getPath:function(){switch(q.prefix.charAt(0)){case "#":return d("hash").slice(q.prefix.length);case "?":return d("search").slice(q.prefix.length)+d("hash");default:return d("pathname").slice(q.prefix.length)+
|
||||
d("search")+d("hash")}},setPath:function(d,f,h){var a={},k={};d=g(d,a,k);if(null!=f){for(var m in f)a[m]=f[m];d=d.replace(/:([^\/]+)/g,function(b,d){delete a[d];return f[d]})}(m=F(a))&&(d+="?"+m);(k=F(k))&&(d+="#"+k);e?(k=h?h.state:null,m=h?h.title:null,b.onpopstate(),h&&h.replace?b.history.replaceState(k,m,q.prefix+d):b.history.pushState(k,m,q.prefix+d)):b.location.href=q.prefix+d},defineRoutes:function(d,h,n){function a(){var a=q.getPath(),e={},f=g(a,e,e),k=b.history.state;if(null!=k)for(var m in k)e[m]=
|
||||
k[m];for(var t in d)if(k=new RegExp("^"+t.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),k.test(f)){f.replace(k,function(){for(var b=t.match(/:[^\/]+/g)||[],f=[].slice.call(arguments,1,-2),g=0;g<b.length;g++)e[b[g].replace(/:|\./g,"")]=decodeURIComponent(f[g]);h(d[t],e,a,t)});return}n(a,e)}e?b.onpopstate=f(a):"#"===q.prefix.charAt(0)&&(b.onhashchange=a);a()}};return q};D.route=function(b,d){var f=V(b),g=function(b){return b},e,n,h,q,m,k=function(b,a,k){if(null==b)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");
|
||||
var t=function(){null!=e&&d.render(b,e(B(n,h.key,h)))},w=function(b){if(b!==a)f.setPath(a,null,{replace:!0});else throw Error("Could not resolve default route "+a);};f.defineRoutes(k,function(a,b,d){var f=m=function(a,k){f===m&&(n=null==k||"function"!==typeof k.view&&"function"!==typeof k?"div":k,h=b,q=d,m=null,e=(a.render||g).bind(a),t())};a.view||"function"===typeof a?f({},a):a.onmatch?U.resolve(a.onmatch(b,d)).then(function(b){f(a,b)},w):f(a,"div")},w);d.subscribe(b,t)};k.set=function(b,a,d){null!=
|
||||
m&&(d=d||{},d.replace=!0);m=null;f.setPath(b,a,d)};k.get=function(){return q};k.prefix=function(b){f.prefix=b};k.link=function(b){b.dom.setAttribute("href",f.prefix+b.attrs.href);b.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(f.prefix)&&(a=a.slice(f.prefix.length)),k.set(a,void 0,void 0))}};k.param=function(b){return"undefined"!==typeof h&&"undefined"!==typeof b?h[b]:h};return k}(window,I);D.withAttr=
|
||||
function(b,d,f){return function(g){d.call(f||this,b in g.currentTarget?g.currentTarget[b]:g.currentTarget.getAttribute(b))}};var W=P(window);D.render=W.render;D.redraw=I.redraw;D.request=L.request;D.jsonp=L.jsonp;D.parseQueryString=M;D.buildQueryString=F;D.version="1.1.6";D.vnode=B;"undefined"!==typeof module?module.exports=D:window.m=D})();
|
||||
(function(){function x(a,d,e,g,q,k){return{tag:a,key:d,attrs:e,children:g,text:q,dom:k,domSize:void 0,state:void 0,events:void 0,instance:void 0}}function Q(a){for(var d in a)if(G.call(a,d))return!1;return!0}function w(a){if(null==a||"string"!==typeof a&&"function"!==typeof a&&"function"!==typeof a.view)throw Error("The selector must be either a string or a component.");var d=arguments[1],e=2;if(null==d)d={};else if("object"!==typeof d||null!=d.tag||Array.isArray(d))d={},e=1;if(arguments.length===
|
||||
e+1){var g=arguments[e];Array.isArray(g)||(g=[g])}else for(g=[];e<arguments.length;)g.push(arguments[e++]);if("string"===typeof a){if(!(e=R[a])){for(var q="div",k=[],h={};e=W.exec(a);){var m=e[1],n=e[2];""===m&&""!==n?q=n:"#"===m?h.id=n:"."===m?k.push(n):"["===e[3][0]&&((m=e[6])&&(m=m.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===e[4]?k.push(m):h[e[4]]=""===m?m:m||!0)}0<k.length&&(h.className=k.join(" "));e=R[a]={tag:q,attrs:h}}g=x.normalizeChildren(g);q=!1;var l,C;k=G.call(d,"class")?
|
||||
"class":"className";h=d[k];if(!Q(e.attrs)&&!Q(d)){m={};for(var c in d)G.call(d,c)&&(m[c]=d[c]);d=m}for(c in e.attrs)G.call(e.attrs,c)&&"className"!==c&&!G.call(d,c)&&(d[c]=e.attrs[c]);if(null!=h||null!=e.attrs.className)d.className=null!=h?null!=e.attrs.className?e.attrs.className+" "+h:h:null!=e.attrs.className?e.attrs.className:null;"class"===k&&(d["class"]=null);for(c in d)if(G.call(d,c)&&"key"!==c){q=!0;break}Array.isArray(g)&&1===g.length&&null!=g[0]&&"#"===g[0].tag?C=g[0].children:l=g;return x(e.tag,
|
||||
d.key,q?d:null,l,C)}return x(a,d.key,d,g)}function X(a){var d=0,e=null,g="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var q=Date.now()-d;null===e&&(e=g(function(){e=null;a();d=Date.now()},16-q))}}x.normalize=function(a){return Array.isArray(a)?x("[",void 0,void 0,x.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?x("#",void 0,void 0,!1===a?"":a,void 0,void 0):a};x.normalizeChildren=function(a){for(var d=[],e=0;e<a.length;e++)d[e]=x.normalize(a[e]);
|
||||
return d};var W=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,R={},G={}.hasOwnProperty;w.trust=function(a){null==a&&(a="");return x("<",void 0,void 0,a,void 0,void 0)};w.fragment=function(a,d){return x("[",a.key,a,x.normalizeChildren(d),void 0,void 0)};var p=function(a){function d(a,c){return function K(d){var F;try{if(!c||null==d||"object"!==typeof d&&"function"!==typeof d||"function"!==typeof(F=d.then))l(function(){c||0!==a.length||console.error("Possible unhandled promise rejection:",
|
||||
d);for(var e=0;e<a.length;e++)a[e](d);q.length=0;k.length=0;n.state=c;n.retry=function(){K(d)}});else{if(d===g)throw new TypeError("Promise can't be resolved w/ itself");e(F.bind(d))}}catch(Y){m(Y)}}}function e(a){function c(c){return function(a){0<d++||c(a)}}var d=0,e=c(m);try{a(c(h),e)}catch(K){e(K)}}if(!(this instanceof p))throw Error("Promise must be called with `new`");if("function"!==typeof a)throw new TypeError("executor must be a function");var g=this,q=[],k=[],h=d(q,!0),m=d(k,!1),n=g._instance=
|
||||
{resolvers:q,rejectors:k},l="function"===typeof setImmediate?setImmediate:setTimeout;e(a)};p.prototype.then=function(a,d){function e(a,d,e,h){d.push(function(c){if("function"!==typeof a)e(c);else try{q(a(c))}catch(y){k&&k(y)}});"function"===typeof g.retry&&h===g.state&&g.retry()}var g=this._instance,q,k,h=new p(function(a,d){q=a;k=d});e(a,g.resolvers,q,!0);e(d,g.rejectors,k,!1);return h};p.prototype["catch"]=function(a){return this.then(null,a)};p.prototype["finally"]=function(a){return this.then(function(d){return p.resolve(a()).then(function(){return d})},
|
||||
function(d){return p.resolve(a()).then(function(){return p.reject(d)})})};p.resolve=function(a){return a instanceof p?a:new p(function(d){d(a)})};p.reject=function(a){return new p(function(d,e){e(a)})};p.all=function(a){return new p(function(d,e){var g=a.length,q=0,k=[];if(0===a.length)d([]);else for(var h=0;h<a.length;h++)(function(m){function n(a){q++;k[m]=a;q===g&&d(k)}null==a[m]||"object"!==typeof a[m]&&"function"!==typeof a[m]||"function"!==typeof a[m].then?n(a[m]):a[m].then(n,e)})(h)})};p.race=
|
||||
function(a){return new p(function(d,e){for(var g=0;g<a.length;g++)a[g].then(d,e)})};"undefined"!==typeof window?("undefined"===typeof window.Promise?window.Promise=p:window.Promise.prototype["finally"]||(window.Promise.prototype["finally"]=p.prototype["finally"]),p=window.Promise):"undefined"!==typeof global&&("undefined"===typeof global.Promise?global.Promise=p:global.Promise.prototype["finally"]||(global.Promise.prototype["finally"]=p.prototype["finally"]),p=global.Promise);var H=function(a){function d(a,
|
||||
g){if(Array.isArray(g))for(var h=0;h<g.length;h++)d(a+"["+h+"]",g[h]);else if("[object Object]"===Object.prototype.toString.call(g))for(h in g)d(a+"["+h+"]",g[h]);else e.push(encodeURIComponent(a)+(null!=g&&""!==g?"="+encodeURIComponent(g):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var e=[],g;for(g in a)d(g,a[g]);return e.join("&")},Z=/^file:\/\//i,O=function(a,d){function e(){function c(){0===--a&&"function"===typeof C&&C()}var a=0;return function D(d){var e=d.then;d.then=
|
||||
function(){a++;var g=e.apply(d,arguments);g.then(c,function(d){c();if(0===a)throw d;});return D(g)};return d}}function g(c,a){if("string"===typeof c){var d=c;c=a||{};null==c.url&&(c.url=d)}return c}function q(c,a){if(null==a)return c;for(var d=c.match(/:[^\/]+/gi)||[],e=0;e<d.length;e++){var g=d[e].slice(1);null!=a[g]&&(c=c.replace(d[e],a[g]))}return c}function k(c,a){var d=H(a);if(""!==d){var e=0>c.indexOf("?")?"?":"&";c+=e+d}return c}function h(c){try{return""!==c?JSON.parse(c):null}catch(y){throw Error("Invalid JSON: "+
|
||||
c);}}function m(c){return c.responseText}function n(c,a){if("function"===typeof c)if(Array.isArray(a))for(var d=0;d<a.length;d++)a[d]=new c(a[d]);else return new c(a);return a}var l=0,C;return{request:function(c,l){var F=e();c=g(c,l);var y=new d(function(d,e){null==c.method&&(c.method="GET");c.method=c.method.toUpperCase();var g="GET"===c.method||"TRACE"===c.method?!1:"boolean"===typeof c.useBody?c.useBody:!0;"function"!==typeof c.serialize&&(c.serialize="undefined"!==typeof FormData&&c.data instanceof
|
||||
FormData?function(c){return c}:JSON.stringify);"function"!==typeof c.deserialize&&(c.deserialize=h);"function"!==typeof c.extract&&(c.extract=m);c.url=q(c.url,c.data);g?c.data=c.serialize(c.data):c.url=k(c.url,c.data);var l=new a.XMLHttpRequest,F=!1,y=l.abort;l.abort=function(){F=!0;y.call(l)};l.open(c.method,c.url,"boolean"===typeof c.async?c.async:!0,"string"===typeof c.user?c.user:void 0,"string"===typeof c.password?c.password:void 0);c.serialize!==JSON.stringify||!g||c.headers&&c.headers.hasOwnProperty("Content-Type")||
|
||||
l.setRequestHeader("Content-Type","application/json; charset=utf-8");c.deserialize!==h||c.headers&&c.headers.hasOwnProperty("Accept")||l.setRequestHeader("Accept","application/json, text/*");c.withCredentials&&(l.withCredentials=c.withCredentials);c.timeout&&(l.timeout=c.timeout);c.responseType&&(l.responseType=c.responseType);for(var C in c.headers)({}).hasOwnProperty.call(c.headers,C)&&l.setRequestHeader(C,c.headers[C]);"function"===typeof c.config&&(l=c.config(l,c)||l);l.onreadystatechange=function(){if(!F&&
|
||||
4===l.readyState)try{var a=c.extract!==m?c.extract(l,c):c.deserialize(c.extract(l,c));if(c.extract!==m||200<=l.status&&300>l.status||304===l.status||Z.test(c.url))d(n(c.type,a));else{var g=Error(l.responseText);g.code=l.status;g.response=a;e(g)}}catch(aa){e(aa)}};g&&null!=c.data?l.send(c.data):l.send()});return!0===c.background?y:F(y)},jsonp:function(c,m){var h=e();c=g(c,m);var y=new d(function(d,e){var g=c.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,m=a.document.createElement("script");
|
||||
a[g]=function(e){m.parentNode.removeChild(m);d(n(c.type,e));delete a[g]};m.onerror=function(){m.parentNode.removeChild(m);e(Error("JSONP request failed"));delete a[g]};null==c.data&&(c.data={});c.url=q(c.url,c.data);c.data[c.callbackKey||"callback"]=g;m.src=k(c.url,c.data);a.document.documentElement.appendChild(m)});return!0===c.background?y:h(y)},setCompletionCallback:function(c){C=c}}}(window,p),V=function(a){function d(t,b){if(t.state!==b)throw Error("`vnode.state` must not be modified");}function e(t){var b=
|
||||
t.state;try{return this.apply(b,arguments)}finally{d(t,b)}}function g(t,b,f,c,a,d,e){for(;f<c;f++){var g=b[f];null!=g&&q(t,g,a,e,d)}}function q(t,b,f,a,d){var n=b.tag;if("string"===typeof n)switch(b.state={},null!=b.attrs&&N(b.attrs,b,f),n){case "#":b.dom=E.createTextNode(b.children);C(t,b.dom,d);break;case "<":k(t,b,a,d);break;case "[":n=E.createDocumentFragment();if(null!=b.children){var l=b.children;g(n,l,0,l.length,f,null,a)}b.dom=n.firstChild;b.domSize=n.childNodes.length;C(t,n,d);break;default:var m=
|
||||
b.tag,u=(n=b.attrs)&&n.is;m=(a=b.attrs&&b.attrs.xmlns||G[b.tag]||a)?u?E.createElementNS(a,m,{is:u}):E.createElementNS(a,m):u?E.createElement(m,{is:u}):E.createElement(m);b.dom=m;if(null!=n)for(l in u=a,n)D(b,l,null,n[l],u);C(t,m,d);if(null!=n&&null!=n.contenteditable)c(b);else if(null!=b.text&&(""!==b.text?m.textContent=b.text:b.children=[x("#",void 0,void 0,b.text,void 0,void 0)]),null!=b.children&&(t=b.children,g(m,t,0,t.length,f,null,a),"select"===b.tag&&null!=n)){if("value"in n)if(null===n.value)-1!==
|
||||
b.dom.selectedIndex&&(b.dom.value=null);else if(f=""+n.value,b.dom.value!==f||-1===b.dom.selectedIndex)b.dom.value=f;"selectedIndex"in n&&D(b,"selectedIndex",null,n.selectedIndex,void 0)}}else{a:{if("function"===typeof b.tag.view){b.state=Object.create(b.tag);n=b.state.view;if(null!=n.$$reentrantLock$$)break a;n.$$reentrantLock$$=!0}else{b.state=void 0;n=b.tag;if(null!=n.$$reentrantLock$$)break a;n.$$reentrantLock$$=!0;b.state=null!=b.tag.prototype&&"function"===typeof b.tag.prototype.view?new b.tag(b):
|
||||
b.tag(b)}null!=b.attrs&&N(b.attrs,b,f);N(b.state,b,f);b.instance=x.normalize(e.call(b.state.view,b));if(b.instance===b)throw Error("A view cannot return the vnode it received as argument");n.$$reentrantLock$$=null}null!=b.instance?(q(t,b.instance,f,a,d),b.dom=b.instance.dom,b.domSize=null!=b.dom?b.instance.domSize:0):b.domSize=0}}function k(t,b,f,c){var a=b.children.match(/^\s*?<(\w+)/im)||[];a=E.createElement(I[a[1]]||"div");"http://www.w3.org/2000/svg"===f?(a.innerHTML='<svg xmlns="http://www.w3.org/2000/svg">'+
|
||||
b.children+"</svg>",a=a.firstChild):a.innerHTML=b.children;b.dom=a.firstChild;b.domSize=a.childNodes.length;for(b=E.createDocumentFragment();f=a.firstChild;)b.appendChild(f);C(t,b,c)}function h(t,b,f,c,a,d){if(b!==f&&(null!=b||null!=f))if(null==b||0===b.length)g(t,f,0,f.length,c,a,d);else if(null==f||0===f.length)y(b,0,b.length);else{for(var e=0,u=0,h=null,k=null;u<b.length;u++)if(null!=b[u]){h=null!=b[u].key;break}for(;e<f.length;e++)if(null!=f[e]){k=null!=f[e].key;break}if(null!==k||null!=h)if(h!==
|
||||
k)y(b,u,b.length),g(t,f,e,f.length,c,a,d);else if(k){k=b.length-1;h=f.length-1;for(var p,r,v,A,z,B;k>=u&&h>=e;)if(A=b[k],z=f[h],null==A)k--;else if(null==z)h--;else if(A.key===z.key)A!==z&&m(t,A,z,c,a,d),null!=z.dom&&(a=z.dom),k--,h--;else break;for(;k>=u&&h>=e;)if(r=b[u],v=f[e],null==r)u++;else if(null==v)e++;else if(r.key===v.key)u++,e++,r!==v&&m(t,r,v,c,l(b,u,a),d);else break;for(;k>=u&&h>=e;){if(null==r)u++;else if(null==v)e++;else if(null==A)k--;else if(null==z)h--;else if(e===h)break;else{if(r.key!==
|
||||
z.key||A.key!==v.key)break;B=l(b,u,a);C(t,n(A),B);A!==v&&m(t,A,v,c,B,d);++e<=--h&&C(t,n(r),a);r!==z&&m(t,r,z,c,a,d);null!=z.dom&&(a=z.dom);u++;k--}A=b[k];z=f[h];r=b[u];v=f[e]}for(;k>=u&&h>=e;){if(null==A)k--;else if(null==z)h--;else if(A.key===z.key)A!==z&&m(t,A,z,c,a,d),null!=z.dom&&(a=z.dom),k--,h--;else break;A=b[k];z=f[h]}if(e>h)y(b,u,k+1);else if(u>k)g(t,f,e,h+1,c,a,d);else{v=a;A=h-e+1;r=Array(A);var x=2147483647,w=0;for(B=0;B<A;B++)r[B]=-1;for(B=h;B>=e;B--){if(null==p){p=b;A=u;z=k+1;for(var D=
|
||||
Object.create(null);A<z;A++){var J=p[A];null!=J&&(J=J.key,null!=J&&(D[J]=A))}p=D}z=f[B];null!=z&&(D=p[z.key],null!=D&&(x=D<x?D:-1,r[B-e]=D,A=b[D],b[D]=null,A!==z&&m(t,A,z,c,a,d),null!=z.dom&&(a=z.dom),w++))}a=v;w!==k-u+1&&y(b,u,k+1);if(0===w)g(t,f,e,h+1,c,a,d);else if(-1===x){u=r.slice();b=[];b.push(0);k=0;for(B=r.length;k<B;++k)if(-1!==r[k])if(v=b[b.length-1],r[v]<r[k])u[k]=v,b.push(k);else{v=0;for(x=b.length-1;v<x;)w=(v+x)/2|0,r[b[w]]<r[k]?v=w+1:x=w;r[k]<r[b[v]]&&(0<v&&(u[k]=b[v-1]),b[v]=k)}v=b.length;
|
||||
for(x=b[v-1];0<v--;)b[v]=x,x=u[x];u=b.length-1;for(B=h;B>=e;B--)v=f[B],-1===r[B-e]?q(t,v,c,d,a):b[u]===B-e?u--:C(t,n(v),a),null!=v.dom&&(a=f[B].dom)}else for(B=h;B>=e;B--)v=f[B],-1===r[B-e]&&q(t,v,c,d,a),null!=v.dom&&(a=f[B].dom)}}else{h=b.length<f.length?b.length:f.length;for(e=e<u?e:u;e<h;e++)r=b[e],v=f[e],r===v||null==r&&null==v||(null==r?q(t,v,c,d,l(b,e+1,a)):null==v?F(r):m(t,r,v,c,l(b,e+1,a),d));b.length>h&&y(b,e,b.length);f.length>h&&g(t,f,e,f.length,c,a,d)}}}function m(a,b,f,d,g,l){var t=b.tag;
|
||||
if(t===f.tag){f.state=b.state;f.events=b.events;var u;var y;null!=f.attrs&&"function"===typeof f.attrs.onbeforeupdate&&(u=e.call(f.attrs.onbeforeupdate,f,b));"string"!==typeof f.tag&&"function"===typeof f.state.onbeforeupdate&&(y=e.call(f.state.onbeforeupdate,f,b));void 0===u&&void 0===y||u||y?u=!1:(f.dom=b.dom,f.domSize=b.domSize,f.instance=b.instance,u=!0);if(!u)if("string"===typeof t)switch(null!=f.attrs&&L(f.attrs,f,d),t){case "#":b.children.toString()!==f.children.toString()&&(b.dom.nodeValue=
|
||||
f.children);f.dom=b.dom;break;case "<":b.children!==f.children?(n(b),k(a,f,l,g)):(f.dom=b.dom,f.domSize=b.domSize);break;case "[":h(a,b.children,f.children,d,g,l);b=0;d=f.children;f.dom=null;if(null!=d){for(var p=0;p<d.length;p++)a=d[p],null!=a&&null!=a.dom&&(null==f.dom&&(f.dom=a.dom),b+=a.domSize||1);1!==b&&(f.domSize=b)}break;default:a=f.dom=b.dom;l=f.attrs&&f.attrs.xmlns||G[f.tag]||l;"textarea"===f.tag&&(null==f.attrs&&(f.attrs={}),null!=f.text&&(f.attrs.value=f.text,f.text=void 0));g=b.attrs;
|
||||
t=f.attrs;u=l;if(null!=t)for(p in t)D(f,p,g&&g[p],t[p],u);var C;if(null!=g)for(p in g)if(null!=(C=g[p])&&(null==t||null==t[p])){y=f;var r=p,v=u;"key"===r||"is"===r||null==C||w(r)||("o"!==r[0]||"n"!==r[1]||w(r)?"style"===r?T(y.dom,C,null):!S(y,r,v)||"className"===r||"option"===y.tag&&"value"===r||"input"===y.tag&&"type"===r?(v=r.indexOf(":"),-1!==v&&(r=r.slice(v+1)),!1!==C&&y.dom.removeAttribute("className"===r?"class":r)):y.dom[r]=null:U(y,r,void 0))}null!=f.attrs&&null!=f.attrs.contenteditable?c(f):
|
||||
null!=b.text&&null!=f.text&&""!==f.text?b.text.toString()!==f.text.toString()&&(b.dom.firstChild.nodeValue=f.text):(null!=b.text&&(b.children=[x("#",void 0,void 0,b.text,void 0,b.dom.firstChild)]),null!=f.text&&(f.children=[x("#",void 0,void 0,f.text,void 0,void 0)]),h(a,b.children,f.children,d,null,l))}else{f.instance=x.normalize(e.call(f.state.view,f));if(f.instance===f)throw Error("A view cannot return the vnode it received as argument");null!=f.attrs&&L(f.attrs,f,d);L(f.state,f,d);null!=f.instance?
|
||||
(null==b.instance?q(a,f.instance,d,l,g):m(a,b.instance,f.instance,d,g,l),f.dom=f.instance.dom,f.domSize=f.instance.domSize):null!=b.instance?(F(b.instance),f.dom=void 0,f.domSize=0):(f.dom=b.dom,f.domSize=b.domSize)}}else F(b),q(a,f,d,l,g)}function n(a){var b=a.domSize;if(null!=b||null==a.dom){var c=E.createDocumentFragment();if(0<b){for(a=a.dom;--b;)c.appendChild(a.nextSibling);c.insertBefore(a,c.firstChild)}return c}return a.dom}function l(a,b,c){for(;b<a.length;b++)if(null!=a[b]&&null!=a[b].dom)return a[b].dom;
|
||||
return c}function C(a,b,c){null!=c?a.insertBefore(b,c):a.appendChild(b)}function c(a){var b=a.children;if(null!=b&&1===b.length&&"<"===b[0].tag)b=b[0].children,a.dom.innerHTML!==b&&(a.dom.innerHTML=b);else if(null!=a.text||null!=b&&0!==b.length)throw Error("Child node of a contenteditable must be trusted");}function y(a,b,c){for(;b<c;b++){var f=a[b];null!=f&&F(f)}}function F(a){function b(){if(++t===c&&(d(a,g),p(a),a.dom)){for(var b=a.dom.parentNode,f=a.domSize||1;--f;)b.removeChild(a.dom.nextSibling);
|
||||
b.removeChild(a.dom)}}var c=1,t=0,g=a.state;if(a.attrs&&"function"===typeof a.attrs.onbeforeremove){var n=e.call(a.attrs.onbeforeremove,a);null!=n&&"function"===typeof n.then&&(c++,n.then(b,b))}"string"!==typeof a.tag&&"function"===typeof a.state.onbeforeremove&&(n=e.call(a.state.onbeforeremove,a),null!=n&&"function"===typeof n.then&&(c++,n.then(b,b)));b()}function p(a){a.attrs&&"function"===typeof a.attrs.onremove&&e.call(a.attrs.onremove,a);if("string"!==typeof a.tag)"function"===typeof a.state.onremove&&
|
||||
e.call(a.state.onremove,a),null!=a.instance&&p(a.instance);else if(a=a.children,Array.isArray(a))for(var b=0;b<a.length;b++){var c=a[b];null!=c&&p(c)}}function D(a,b,c,d,e){if("key"!==b&&"is"!==b&&null!=d&&!w(b)&&(c!==d||"value"===b||"checked"===b||"selectedIndex"===b||"selected"===b&&a.dom===E.activeElement||"option"===a.tag&&a.dom.parentNode===E.activeElement||"object"===typeof d)){if("o"===b[0]&&"n"===b[1])return U(a,b,d);"xlink:"===b.slice(0,6)?a.dom.setAttributeNS("http://www.w3.org/1999/xlink",
|
||||
b.slice(6),d):"style"===b?T(a.dom,c,d):S(a,b,e)?"value"===b&&(("input"===a.tag||"textarea"===a.tag)&&a.dom.value===""+d&&a.dom===E.activeElement||"select"===a.tag&&null!==c&&a.dom.value===""+d||"option"===a.tag&&null!==c&&a.dom.value===""+d)||("input"===a.tag&&"type"===b?a.dom.setAttribute(b,d):a.dom[b]=d):"boolean"===typeof d?d?a.dom.setAttribute(b,""):a.dom.removeAttribute(b):a.dom.setAttribute("className"===b?"class":b,d)}}function w(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===
|
||||
a||"onbeforeremove"===a||"onbeforeupdate"===a}function S(a,b,c){return void 0===c&&(-1<a.tag.indexOf("-")||null!=a.attrs&&a.attrs.is||"href"!==b&&"list"!==b&&"form"!==b&&"width"!==b&&"height"!==b)&&b in a.dom}function T(a,b,c){if(null!=b&&null!=c&&"object"===typeof b&&"object"===typeof c&&c!==b){for(var d in c)c[d]!==b[d]&&(a.style[d]=c[d]);for(d in b)d in c||(a.style[d]="")}else if(b===c&&(a.style.cssText="",b=null),null==c)a.style.cssText="";else if("string"===typeof c)a.style.cssText=c;else for(d in"string"===
|
||||
typeof b&&(a.style.cssText=""),c)a.style[d]=c[d]}function M(){}function U(a,b,c){null!=a.events?a.events[b]!==c&&(null==c||"function"!==typeof c&&"object"!==typeof c?(null!=a.events[b]&&a.dom.removeEventListener(b.slice(2),a.events,!1),a.events[b]=void 0):(null==a.events[b]&&a.dom.addEventListener(b.slice(2),a.events,!1),a.events[b]=c)):null==c||"function"!==typeof c&&"object"!==typeof c||(a.events=new M,a.dom.addEventListener(b.slice(2),a.events,!1),a.events[b]=c)}function N(a,b,c){"function"===
|
||||
typeof a.oninit&&e.call(a.oninit,b);"function"===typeof a.oncreate&&c.push(e.bind(a.oncreate,b))}function L(a,b,c){"function"===typeof a.onupdate&&c.push(e.bind(a.onupdate,b))}var E=a.document,G={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"},H,I={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"};M.prototype=Object.create(null);M.prototype.handleEvent=function(a){var b=this["on"+a.type],c;"function"===
|
||||
typeof b?c=b.call(a.target,a):"function"===typeof b.handleEvent&&b.handleEvent(a);"function"===typeof H&&H.call(a.target,a);!1===c&&(a.preventDefault(),a.stopPropagation())};return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],d=E.activeElement,e=a.namespaceURI;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);h(a,a.vnodes,x.normalizeChildren(b),c,null,"http://www.w3.org/1999/xhtml"===e?void 0:e);a.vnodes=
|
||||
b;null!=d&&E.activeElement!==d&&"function"===typeof d.focus&&d.focus();for(d=0;d<c.length;d++)c[d]()},setEventCallback:function(a){return H=a}}},I=function(a,d){function e(a){a=k.indexOf(a);-1<a&&k.splice(a,2)}function g(){if(h)throw Error("Nested m.redraw.sync() call");h=!0;for(var a=1;a<k.length;a+=2)try{k[a]()}catch(l){"undefined"!==typeof console&&console.error(l)}h=!1}var q=V(a);q.setEventCallback(function(a){!1===a.redraw?a.redraw=void 0:m()});var k=[],h=!1,m=(d||X)(g);m.sync=g;return{subscribe:function(a,
|
||||
d){e(a);k.push(a,d)},unsubscribe:e,redraw:m,render:q.render}}(window);O.setCompletionCallback(I.redraw);w.mount=function(a){return function(d,e){if(null===e)a.render(d,[]),a.unsubscribe(d);else{if(null==e.view&&"function"!==typeof e)throw Error("m.mount(element, component) expects a component, not a vnode");var g=function(){a.render(d,x(e))};a.subscribe(d,g);g()}}}(I);var ba=p,P=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var d={},e={},g=0;g<a.length;g++){var q=
|
||||
a[g].split("="),k=decodeURIComponent(q[0]);q=2===q.length?decodeURIComponent(q[1]):"";"true"===q?q=!0:"false"===q&&(q=!1);var h=k.split(/\]\[?|\[/),m=d;-1<k.indexOf("[")&&h.pop();for(var n=0;n<h.length;n++){k=h[n];var l=h[n+1];l=""==l||!isNaN(parseInt(l,10));var p=n===h.length-1;""===k&&(k=h.slice(0,n).join(),null==e[k]&&(e[k]=0),k=e[k]++);null==m[k]&&(m[k]=p?q:l?[]:{});m=m[k]}}return d},ca=function(a){function d(d){var e=a.location[d].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===
|
||||
d&&"/"!==e[0]&&(e="/"+e);return e}function e(a){return function(){null==h&&(h=k(function(){h=null;a()}))}}function g(a,d,e){var c=a.indexOf("?"),g=a.indexOf("#"),h=-1<c?c:-1<g?g:a.length;if(-1<c){c=P(a.slice(c+1,-1<g?g:a.length));for(var k in c)d[k]=c[k]}if(-1<g)for(k in d=P(a.slice(g+1)),d)e[k]=d[k];return a.slice(0,h)}var q="function"===typeof a.history.pushState,k="function"===typeof setImmediate?setImmediate:setTimeout,h,m={prefix:"#!",getPath:function(){switch(m.prefix.charAt(0)){case "#":return d("hash").slice(m.prefix.length);
|
||||
case "?":return d("search").slice(m.prefix.length)+d("hash");default:return d("pathname").slice(m.prefix.length)+d("search")+d("hash")}},setPath:function(d,e,k){var c={},h={};d=g(d,c,h);if(null!=e){for(var l in e)c[l]=e[l];d=d.replace(/:([^\/]+)/g,function(a,d){delete c[d];return e[d]})}(l=H(c))&&(d+="?"+l);(h=H(h))&&(d+="#"+h);q?(h=k?k.state:null,l=k?k.title:null,a.onpopstate(),k&&k.replace?a.history.replaceState(h,l,m.prefix+d):a.history.pushState(h,l,m.prefix+d)):a.location.href=m.prefix+d},defineRoutes:function(d,
|
||||
k,h){function c(){var c=m.getPath(),e={},l=g(c,e,e),n=a.history.state;if(null!=n)for(var q in n)e[q]=n[q];for(var p in d)if(n=new RegExp("^"+p.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),n.test(l)){l.replace(n,function(){for(var a=p.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),h=0;h<a.length;h++)e[a[h].replace(/:|\./g,"")]=decodeURIComponent(g[h]);k(d[p],e,c,p)});return}h(c,e)}q?a.onpopstate=e(c):"#"===m.prefix.charAt(0)&&(a.onhashchange=c);c()}};return m};w.route=
|
||||
function(a,d){var e=ca(a),g=function(a){return a},p,k,h,m,n,l=function(a,l,q){function c(){null!=p&&d.render(a,p(x(k,h.key,h)))}if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var y=function(){c();y=d.redraw};d.subscribe(a,c);var w=function(a){if(a!==l)e.setPath(l,null,{replace:!0});else throw Error("Could not resolve default route "+l);};e.defineRoutes(q,function(a,c,d){var e=n=function(a,l){e===n&&(k=null==l||"function"!==typeof l.view&&"function"!==
|
||||
typeof l?"div":l,h=c,m=d,n=null,p=(a.render||g).bind(a),y())};a.view||"function"===typeof a?e({},a):a.onmatch?ba.resolve(a.onmatch(c,d)).then(function(c){e(a,c)},w):e(a,"div")},w)};l.set=function(a,d,g){null!=n&&(g=g||{},g.replace=!0);n=null;e.setPath(a,d,g)};l.get=function(){return m};l.prefix=function(a){e.prefix=a};var w=function(a,d){d.dom.setAttribute("href",e.prefix+d.attrs.href);d.dom.onclick=function(c){c.ctrlKey||c.metaKey||c.shiftKey||2===c.which||(c.preventDefault(),c.redraw=!1,c=this.getAttribute("href"),
|
||||
0===c.indexOf(e.prefix)&&(c=c.slice(e.prefix.length)),l.set(c,void 0,a))}};l.link=function(a){return null==a.tag?w.bind(w,a):w({},a)};l.param=function(a){return"undefined"!==typeof h&&"undefined"!==typeof a?h[a]:h};return l}(window,I);w.withAttr=function(a,d,e){return function(g){d.call(e||this,a in g.currentTarget?g.currentTarget[a]:g.currentTarget.getAttribute(a))}};var da=V(window);w.render=da.render;w.redraw=I.redraw;w.request=O.request;w.jsonp=O.jsonp;w.parseQueryString=P;w.buildQueryString=
|
||||
H;w.version="1.1.3";w.vnode=x;w.PromisePolyfill=p;"undefined"!==typeof module?module.exports=w:window.m=w})();
|
||||
148
ospec/README.md
148
ospec/README.md
|
|
@ -1,13 +1,13 @@
|
|||
ospec [](https://www.npmjs.com/package/ospec) [](https://www.npmjs.com/package/ospec)
|
||||
=====
|
||||
|
||||
[About](#about) | [Usage](#usage) | [API](#api) | [Goals](#goals)
|
||||
[About](#about) | [Usage](#usage) | [CLI](#command-line-interface) | [API](#api) | [Goals](#goals)
|
||||
|
||||
Noiseless testing framework
|
||||
|
||||
## About
|
||||
|
||||
- ~330 LOC including the CLI runner
|
||||
- ~360 LOC including the CLI runner
|
||||
- terser and faster test code than with mocha, jasmine or tape
|
||||
- test code reads like bullet points
|
||||
- assertion code follows [SVO](https://en.wikipedia.org/wiki/Subject–verb–object) structure in present tense for terseness and readability
|
||||
|
|
@ -111,6 +111,7 @@ o.spec("call()", function() {
|
|||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args[0]).equals(1)
|
||||
o(spy.calls[0]).deepEquals([1])
|
||||
})
|
||||
})
|
||||
```
|
||||
|
|
@ -164,30 +165,54 @@ o("promise test", async function() {
|
|||
})
|
||||
```
|
||||
|
||||
By default, asynchronous tests time out after 20ms. This can be changed on a per-test basis using the `timeout` argument:
|
||||
#### Timeout delays
|
||||
|
||||
By default, asynchronous tests time out after 200ms. You can change that default for the current test suite and
|
||||
its children by using the `o.specTimeout(delay)` function.
|
||||
|
||||
```javascript
|
||||
o.spec("a spec that must timeout quickly", function(done, timeout) {
|
||||
// wait 20ms before bailing out of the tests of this suite and
|
||||
// its descendants
|
||||
o.specTimeout(20)
|
||||
o("some test", function(done) {
|
||||
setTimeout(done, 10) // this will pass
|
||||
})
|
||||
|
||||
o.spec("a child suite where the delay also applies", function () {
|
||||
o("some test", function(done) {
|
||||
setTimeout(done, 30) // this will time out.
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("a spec that uses the default delay", function() {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
This can also be changed on a per-test basis using the `o.timeout(delay)` function from within a test:
|
||||
|
||||
```javascript
|
||||
o("setTimeout calls callback", function(done, timeout) {
|
||||
timeout(50) //wait 50ms before bailing out of the test
|
||||
o.timeout(500) //wait 500ms before bailing out of the test
|
||||
|
||||
setTimeout(done, 30)
|
||||
setTimeout(done, 300)
|
||||
})
|
||||
```
|
||||
|
||||
Note that the `timeout` function call must be the first statement in its test. This currently does not work for promise tests. You can combine both methods to do this:
|
||||
Note that the `o.timeout` function call must be the first statement in its test. It also works with Promise-returning tests:
|
||||
|
||||
```javascript
|
||||
o("promise test", function(done, timeout) {
|
||||
timeout(1000)
|
||||
someOtherAsyncFunctionThatTakes900ms().then(done)
|
||||
o("promise test", function() {
|
||||
o.timeout(1000)
|
||||
return someOtherAsyncFunctionThatTakes900ms()
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
o("promise test", async function(done, timeout) {
|
||||
timeout(1000)
|
||||
o("promise test", async function() {
|
||||
o.timeout(1000)
|
||||
await someOtherAsyncFunctionThatTakes900ms()
|
||||
done()
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -247,20 +272,31 @@ o.spec("math", function() {
|
|||
})
|
||||
```
|
||||
|
||||
### Running only one test
|
||||
### Running only some tests
|
||||
|
||||
A test can be temporarily made to run exclusively by calling `o.only()` instead of `o`. This is useful when troubleshooting regressions, to zero-in on a failing test, and to avoid saturating console log w/ irrelevant debug information.
|
||||
One or more tests can be temporarily made to run exclusively by calling `o.only()` instead of `o`. This is useful when troubleshooting regressions, to zero-in on a failing test, and to avoid saturating console log w/ irrelevant debug information.
|
||||
|
||||
```javascript
|
||||
o.spec("math", function() {
|
||||
// will not run
|
||||
o("addition", function() {
|
||||
o(1 + 1).equals(2)
|
||||
})
|
||||
|
||||
//only this test will be run, regardless of how many groups there are
|
||||
// this test will be run, regardless of how many groups there are
|
||||
o.only("subtraction", function() {
|
||||
o(1 - 1).notEquals(2)
|
||||
})
|
||||
|
||||
// will not run
|
||||
o("multiplication", function() {
|
||||
o(2 * 2).equals(4)
|
||||
})
|
||||
|
||||
// this test will be run, regardless of how many groups there are
|
||||
o.only("division", function() {
|
||||
o(6 / 2).notEquals(2)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -288,28 +324,64 @@ _o("a test", function() {
|
|||
_o.run()
|
||||
```
|
||||
|
||||
### Running the test suite from the command-line
|
||||
## Command Line Interface
|
||||
|
||||
ospec will automatically evaluate all `*.js` files in any folder named `/tests`.
|
||||
|
||||
`o.run()` is automatically called by the cli - no need to call it in your test code.
|
||||
|
||||
#### Create an npm script in your package:
|
||||
Create a script in your package.json:
|
||||
```
|
||||
"scripts": {
|
||||
...
|
||||
"test": "ospec",
|
||||
...
|
||||
}
|
||||
```
|
||||
...and run it from the command line:
|
||||
|
||||
```
|
||||
$ npm test
|
||||
$ npm test
|
||||
```
|
||||
|
||||
#### Direct use from the command line
|
||||
**NOTE:** `o.run()` is automatically called by the cli - no need to call it in your test code.
|
||||
|
||||
Ospec doesn't work when installed globally. Using global scripts is generally a bad idea since you can end up with different, incompatible versions of the same package installed locally and globally.
|
||||
### CLI Options
|
||||
|
||||
Running ospec without arguments is equivalent to running `ospec '**/tests/**/*.js'`. In english, this tells ospec to evaluate all `*.js` files in any sub-folder named `tests/` (the `node_modules` folder is always excluded).
|
||||
|
||||
If you wish to change this behavior, just provide one or more glob match patterns:
|
||||
|
||||
```
|
||||
ospec '**/spec/**/*.js' '**/*.spec.js'
|
||||
```
|
||||
|
||||
You can also provide ignore patterns (note: always add `--ignore` AFTER match patterns):
|
||||
|
||||
```
|
||||
ospec --ignore 'folder1/**' 'folder2/**'
|
||||
```
|
||||
|
||||
Finally, you may choose to load files or modules before any tests run (**note:** always add `--require` AFTER match patterns):
|
||||
|
||||
```
|
||||
ospec --require esm
|
||||
```
|
||||
|
||||
Here's an example of mixing them all together:
|
||||
|
||||
```
|
||||
ospec '**/*.test.js' --ignore 'folder1/**' --require esm ./my-file.js
|
||||
```
|
||||
|
||||
### Run ospec directly from the command line:
|
||||
|
||||
ospec comes with an executable named `ospec`. NPM auto-installs local binaries to `./node_modules/.bin/`. You can run ospec by running `./node_modules/.bin/ospec` from your project root, but there are more convenient methods to do so that we will soon describe.
|
||||
|
||||
ospec doesn't work when installed globally (`npm install -g`). Using global scripts is generally a bad idea since you can end up with different, incompatible versions of the same package installed locally and globally.
|
||||
|
||||
Here are different ways of running ospec from the command line. This knowledge applies to not just ospec, but any locally installed npm binary.
|
||||
|
||||
#### npx
|
||||
|
||||
If you're using a recent version of npm (v5+), you can use run `npx ospec` from your project folder.
|
||||
|
||||
#### npm-run
|
||||
|
||||
If you're using a recent version of npm (v5+), you can use run `npx ospec` from your project folder.
|
||||
|
||||
|
|
@ -325,6 +397,16 @@ Then, from a project that has ospec installed as a (dev) dependency:
|
|||
npm-run ospec
|
||||
```
|
||||
|
||||
#### PATH
|
||||
|
||||
If you understand how your system's PATH works (e.g. for [OSX](https://coolestguidesontheplanet.com/add-shell-path-osx/)), then you can add the following to your PATH...
|
||||
|
||||
```
|
||||
export PATH=./node_modules/.bin:$PATH
|
||||
```
|
||||
|
||||
...and you'll be able to run `ospec` without npx, npm, etc. This one-time setup will also work with other binaries across all your node projects, as long as you run binaries from the root of your projects.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
|
@ -491,15 +573,23 @@ o.run(function(results) {
|
|||
|
||||
---
|
||||
|
||||
### Boolean result.pass
|
||||
### Boolean|Null result.pass
|
||||
|
||||
True if the test passed. **No other keys will exist on the result if this value is true.**
|
||||
- `true` if the assertion passed.
|
||||
- `false` if the assertion failed.
|
||||
- `null` if the assertion was incomplete (`o("partial assertion) // and that's it`).
|
||||
|
||||
---
|
||||
|
||||
### Error result.error
|
||||
|
||||
The `Error` object explaining the reason behind a failure.
|
||||
The `Error` object explaining the reason behind a failure. If the assertion failed, the stack will point to the actuall error. If the assertion did pass or was incomplete, this field is identical to `result.testError`.
|
||||
|
||||
---
|
||||
|
||||
### Error result.testError
|
||||
|
||||
An `Error` object whose stack points to the test definition that wraps the assertion. Useful as a fallback because in some async cases the main may not point to test code.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -527,7 +617,7 @@ o.spec("message", function() {
|
|||
|
||||
### String result.context
|
||||
|
||||
A `>`-separated string showing the structure of the test specification.
|
||||
In case of failure, a `>`-separated string showing the structure of the test specification.
|
||||
In the below example, `result.context` would be `testing > rocks`.
|
||||
|
||||
```javascript
|
||||
|
|
|
|||
70
ospec/bin/ospec
Normal file → Executable file
70
ospec/bin/ospec
Normal file → Executable file
|
|
@ -1,48 +1,42 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict"
|
||||
|
||||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
|
||||
var o = require("../ospec")
|
||||
var path = require("path")
|
||||
var glob = require("glob")
|
||||
|
||||
function traverseDirectory(pathname, callback) {
|
||||
pathname = pathname.replace(/\\/g, "/")
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.lstat(pathname, function(err, stat) {
|
||||
if (err) reject(err)
|
||||
if (stat && stat.isDirectory()) {
|
||||
fs.readdir(pathname, function(err, pathnames) {
|
||||
if (err) reject(err)
|
||||
var promises = []
|
||||
for (var i = 0; i < pathnames.length; i++) {
|
||||
if (pathnames[i] === "node_modules") continue
|
||||
if (pathnames[i][0] === ".") continue
|
||||
pathnames[i] = path.join(pathname, pathnames[i])
|
||||
promises.push(traverseDirectory(pathnames[i], callback))
|
||||
}
|
||||
callback(pathname, stat, pathnames)
|
||||
resolve(Promise.all(promises))
|
||||
})
|
||||
}
|
||||
else {
|
||||
callback(pathname, stat)
|
||||
resolve(pathname)
|
||||
}
|
||||
})
|
||||
|
||||
function parseArgs(argv) {
|
||||
argv = ["--globs"].concat(argv.slice(2))
|
||||
var args = {}
|
||||
var name
|
||||
argv.forEach(function(arg) {
|
||||
if (/^--/.test(arg)) {
|
||||
name = arg.substr(2)
|
||||
args[name] = args[name] || []
|
||||
} else {
|
||||
args[name].push(arg)
|
||||
}
|
||||
})
|
||||
return args
|
||||
}
|
||||
|
||||
traverseDirectory(".", function(pathname) {
|
||||
if (pathname.match(/(?:^|\/)tests\/.*\.js$/)) {
|
||||
require(path.normalize(process.cwd()) + "/" + pathname) // eslint-disable-line global-require
|
||||
}
|
||||
})
|
||||
.then(o.run)
|
||||
.catch(function(e) {
|
||||
console.log(e.stack)
|
||||
|
||||
var args = parseArgs(process.argv)
|
||||
var globList = args.globs && args.globs.length ? args.globs : ["**/tests/**/*.js"]
|
||||
var ignore = ["**/node_modules/**"].concat(args.ignore||[])
|
||||
var cwd = process.cwd()
|
||||
|
||||
args.require && args.require.forEach(function(module) {
|
||||
module && require(require.resolve(module, { basedir: cwd }))
|
||||
})
|
||||
|
||||
process.on("unhandledRejection", function(e) {
|
||||
console.log("Uncaught (in promise) " + e.stack)
|
||||
})
|
||||
var pending = globList.length
|
||||
globList.forEach(function(globPattern) {
|
||||
glob(globPattern, {ignore: ignore})
|
||||
.on("match", function(fileName) { require(path.join(cwd, fileName)) }) // eslint-disable-line global-require
|
||||
.on("error", function(e) { console.error(e) })
|
||||
.on("end", function() { if (--pending === 0) o.run()})
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", function(e) { console.error("Uncaught (in promise) " + e.stack) })
|
||||
|
|
|
|||
77
ospec/change-log.md
Normal file
77
ospec/change-log.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Change Log for ospec
|
||||
|
||||
|
||||
## Upcoming...
|
||||
_2018-xx-yy_
|
||||
<!-- Add new lines here. Version number will be decided later -->
|
||||
- Add `spy.calls` array property to get the `this` and `arguments` values for any arbitrary call.
|
||||
|
||||
## 3.0.1
|
||||
_2018-06-30_
|
||||
|
||||
### Bug fix
|
||||
- Move `glob` from `devDependencies` to `dependencies`, fix the test runner ([#2186](https://github.com/MithrilJS/mithril.js/pull/2186) [@porsager](https://github.com/porsager)
|
||||
|
||||
## 3.0.0
|
||||
_2018-06-20_
|
||||
### Breaking
|
||||
- Better input checking to prevent misuses of the library. Misues of the library will now throw errors, rather than report failures. This may uncover bugs in your test suites. Since it is potentially a disruptive update this change triggers a semver major bump. ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- Change the reserved character for hooks and test suite meta-information from `"__"` to `"\x01"`. Tests whose name start with `"\0x01"` will be rejected ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
|
||||
### Features
|
||||
- Give async timeout a stack trace that points to the problematic test ([#2154](https://github.com/MithrilJS/mithril.js/pull/2154) [@gilbert](github.com/gilbert), [#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- deprecate the `timeout` parameter in async tests in favour of `o.timeout()` for setting the timeout delay. The `timeout` parameter still works for v3, and will be removed in v4 ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- add `o.defaultTimeout()` for setting the the timeout delay for the current spec and its children ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- adds the possibility to select more than one test with o.only ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171))
|
||||
|
||||
### Bug fixes
|
||||
- Detect duplicate calls to `done()` properly [#2162](https://github.com/MithrilJS/mithril.js/issues/2162) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- Don't try to report internal errors as assertion failures, throw them instead ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- Don't ignore, silently, tests whose name start with the test suite meta-information sequence (was `"__"` up to this version) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- Fix the `done()` call detection logic [#2158](https://github.com/MithrilJS/mithril.js/issues/2158) and assorted fixes (accept non-English names, tolerate comments) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167))
|
||||
- Catch exceptions thrown in synchronous tests and report them as assertion failures ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171))
|
||||
- Fix a stack overflow when using `o.only()` with a large test suite ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171))
|
||||
|
||||
## 2.1.0
|
||||
_2018-05-25_
|
||||
### Features
|
||||
- Pinpoint the `o.only()` call site ([#2157](https://github.com/MithrilJS/mithril.js/pull/2157))
|
||||
- Improved wording, spacing and color-coding of report messages and errors ([#2147](https://github.com/MithrilJS/mithril.js/pull/2147), [@maranomynet](https://github.com/maranomynet))
|
||||
|
||||
### Bug fixes
|
||||
- Convert the exectuable back to plain ES5 [#2160](https://github.com/MithrilJS/mithril.js/issues/2160) ([#2161](https://github.com/MithrilJS/mithril.js/pull/2161))
|
||||
|
||||
|
||||
## 2.0.0
|
||||
_2018-05-09_
|
||||
- Added `--require` feature to the ospec executable ([#2144](https://github.com/MithrilJS/mithril.js/pull/2144), [@gilbert](https://github.com/gilbert))
|
||||
- In Node.js, ospec only uses colors when the output is sent to a terminal ([#2143](https://github.com/MithrilJS/mithril.js/pull/2143))
|
||||
- the CLI runner now accepts globs as arguments ([#2141](https://github.com/MithrilJS/mithril.js/pull/2141), [@maranomynet](https://github.com/maranomynet))
|
||||
- Added support for custom reporters ([#2020](https://github.com/MithrilJS/mithril.js/pull/2020), [@zyrolasting](https://github.com/zyrolasting))
|
||||
- Make ospec more [Flems](https://flems.io)-friendly ([#2034](https://github.com/MithrilJS/mithril.js/pull/2034))
|
||||
- Works either as a global or in CommonJS environments
|
||||
- the o.run() report is always printed asynchronously (it could be synchronous before if none of the tests were async).
|
||||
- Properly point to the assertion location of async errors [#2036](https://github.com/MithrilJS/mithril.js/issues/2036)
|
||||
- expose the default reporter as `o.report(results)`
|
||||
- Don't try to access the stack traces in IE9
|
||||
|
||||
|
||||
|
||||
## 1.4.1
|
||||
_2018-05-03_
|
||||
- Identical to v1.4.0, but with UNIX-style line endings so that BASH is happy.
|
||||
|
||||
|
||||
|
||||
## 1.4.0
|
||||
_2017-12-01_
|
||||
- Added support for async functions and promises in tests ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928), [@StephanHoyer](https://github.com/StephanHoyer))
|
||||
- Error handling for async tests with `done` callbacks supports error as first argument ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928))
|
||||
- Error messages which include newline characters do not swallow the stack trace [#1495](https://github.com/MithrilJS/mithril.js/issues/1495) ([#1984](https://github.com/MithrilJS/mithril.js/pull/1984), [@RodericDay](https://github.com/RodericDay))
|
||||
|
||||
|
||||
|
||||
## 1.3 and earlier
|
||||
- Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager))
|
||||
- Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout))
|
||||
- Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529))
|
||||
279
ospec/ospec.js
279
ospec/ospec.js
|
|
@ -1,11 +1,12 @@
|
|||
/* eslint-disable global-require, no-bitwise, no-process-exit */
|
||||
"use strict"
|
||||
;(function(m) {
|
||||
if (typeof module !== "undefined") module["exports"] = m()
|
||||
else window.o = m()
|
||||
})(function init(name) {
|
||||
var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty
|
||||
|
||||
var spec = {}, subjects = [], results, only = [], ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty
|
||||
var ospecFileName = getStackName(ensureStackTrace(new Error), /[\/\\](.*?):\d+:\d+/), timeoutStackName
|
||||
var globalTimeout = noTimeoutRightNow
|
||||
var currentTestError = null
|
||||
if (name != null) spec[name] = ctx = {}
|
||||
|
||||
try {throw new Error} catch (e) {
|
||||
|
|
@ -13,19 +14,25 @@ else window.o = m()
|
|||
}
|
||||
function o(subject, predicate) {
|
||||
if (predicate === undefined) {
|
||||
if (results == null) throw new Error("Assertions should not occur outside test definitions")
|
||||
if (!isRunning()) throw new Error("Assertions should not occur outside test definitions")
|
||||
return new Assert(subject)
|
||||
}
|
||||
else if (results == null) {
|
||||
ctx[unique(subject)] = predicate
|
||||
} else {
|
||||
throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`")
|
||||
if (isRunning()) throw new Error("Test definitions and hooks shouldn't be nested. To group tests use `o.spec()`")
|
||||
subject = String(subject)
|
||||
if (subject.charCodeAt(0) === 1) throw new Error("test names starting with '\\x01' are reserved for internal use")
|
||||
ctx[unique(subject)] = new Task(predicate, ensureStackTrace(new Error))
|
||||
}
|
||||
}
|
||||
o.before = hook("__before")
|
||||
o.after = hook("__after")
|
||||
o.beforeEach = hook("__beforeEach")
|
||||
o.afterEach = hook("__afterEach")
|
||||
o.before = hook("\x01before")
|
||||
o.after = hook("\x01after")
|
||||
o.beforeEach = hook("\x01beforeEach")
|
||||
o.afterEach = hook("\x01afterEach")
|
||||
o.specTimeout = function (t) {
|
||||
if (isRunning()) throw new Error("o.specTimeout() can only be called before o.run()")
|
||||
if (hasOwn.call(ctx, "\x01specTimeout")) throw new Error("A default timeout has already been defined in this context")
|
||||
if (typeof t !== "number") throw new Error("o.specTimeout() expects a number as argument")
|
||||
ctx["\x01specTimeout"] = t
|
||||
}
|
||||
o.new = init
|
||||
o.spec = function(subject, predicate) {
|
||||
var parent = ctx
|
||||
|
|
@ -34,13 +41,18 @@ else window.o = m()
|
|||
ctx = parent
|
||||
}
|
||||
o.only = function(subject, predicate, silent) {
|
||||
if (!silent) console.log(highlight("/!\\ WARNING /!\\ o.only() mode"))
|
||||
o(subject, only = predicate)
|
||||
if (!silent) console.log(
|
||||
highlight("/!\\ WARNING /!\\ o.only() mode") + "\n" + o.cleanStackTrace(ensureStackTrace(new Error)) + "\n",
|
||||
cStyle("red"), ""
|
||||
)
|
||||
only.push(predicate)
|
||||
o(subject, predicate)
|
||||
}
|
||||
o.spy = function(fn) {
|
||||
var spy = function() {
|
||||
spy.this = this
|
||||
spy.args = [].slice.call(arguments)
|
||||
spy.calls.push({this: this, args: spy.args})
|
||||
spy.callCount++
|
||||
|
||||
if (fn) return fn.apply(this, arguments)
|
||||
|
|
@ -51,6 +63,7 @@ else window.o = m()
|
|||
name: {value: fn.name}
|
||||
})
|
||||
spy.args = []
|
||||
spy.calls = []
|
||||
spy.callCount = 0
|
||||
return spy
|
||||
}
|
||||
|
|
@ -67,104 +80,125 @@ else window.o = m()
|
|||
}
|
||||
if (ospecFileName == null) return stack.join("\n")
|
||||
// skip ospec-related entries on the stack
|
||||
while (stack[i].indexOf(ospecFileName) !== -1) i++
|
||||
// now we're in user code
|
||||
while (stack[i] != null && stack[i].indexOf(ospecFileName) !== -1) i++
|
||||
// now we're in user code (or past the stack end)
|
||||
return stack[i]
|
||||
}
|
||||
o.timeout = function(n) {
|
||||
globalTimeout(n)
|
||||
}
|
||||
o.run = function(reporter) {
|
||||
results = []
|
||||
start = new Date
|
||||
test(spec, [], [], function() {
|
||||
test(spec, [], [], new Task(function() {
|
||||
setTimeout(function () {
|
||||
timeoutStackName = getStackName({stack: o.cleanStackTrace(ensureStackTrace(new Error))}, /([\w \.]+?:\d+:\d+)/)
|
||||
if (typeof reporter === "function") reporter(results)
|
||||
else {
|
||||
var errCount = o.report(results)
|
||||
if (hasProcess && errCount !== 0) process.exit(1)
|
||||
if (hasProcess && errCount !== 0) process.exit(1) // eslint-disable-line no-process-exit
|
||||
}
|
||||
})
|
||||
})
|
||||
}, null), 200 /*default timeout delay*/)
|
||||
|
||||
function test(spec, pre, post, finalize) {
|
||||
pre = [].concat(pre, spec["__beforeEach"] || [])
|
||||
post = [].concat(spec["__afterEach"] || [], post)
|
||||
series([].concat(spec["__before"] || [], Object.keys(spec).map(function(key) {
|
||||
return function(done, timeout) {
|
||||
timeout(Infinity)
|
||||
|
||||
if (key.slice(0, 2) === "__") return done()
|
||||
if (only !== null && spec[key] !== only && typeof only === typeof spec[key]) return done()
|
||||
subjects.push(key)
|
||||
var type = typeof spec[key]
|
||||
if (type === "object") test(spec[key], pre, post, pop)
|
||||
if (type === "function") series([].concat(pre, spec[key], post, pop))
|
||||
|
||||
function pop() {
|
||||
subjects.pop()
|
||||
done()
|
||||
}
|
||||
function test(spec, pre, post, finalize, defaultDelay) {
|
||||
if (hasOwn.call(spec, "\x01specTimeout")) defaultDelay = spec["\x01specTimeout"]
|
||||
pre = [].concat(pre, spec["\x01beforeEach"] || [])
|
||||
post = [].concat(spec["\x01afterEach"] || [], post)
|
||||
series([].concat(spec["\x01before"] || [], Object.keys(spec).reduce(function(tasks, key) {
|
||||
if (key.charCodeAt(0) !== 1 && (only.length === 0 || only.indexOf(spec[key].fn) !== -1 || !(spec[key] instanceof Task))) {
|
||||
tasks.push(new Task(function(done) {
|
||||
o.timeout(Infinity)
|
||||
subjects.push(key)
|
||||
var pop = new Task(function pop() {subjects.pop(), done()}, null)
|
||||
if (spec[key] instanceof Task) series([].concat(pre, spec[key], post, pop), defaultDelay)
|
||||
else test(spec[key], pre, post, pop, defaultDelay)
|
||||
}, null))
|
||||
}
|
||||
}), spec["__after"] || [], finalize))
|
||||
return tasks
|
||||
}, []), spec["\x01after"] || [], finalize), defaultDelay)
|
||||
}
|
||||
|
||||
function series(fns) {
|
||||
function series(tasks, defaultDelay) {
|
||||
var cursor = 0
|
||||
next()
|
||||
|
||||
function next() {
|
||||
if (cursor === fns.length) return
|
||||
if (cursor === tasks.length) return
|
||||
|
||||
var task = tasks[cursor++]
|
||||
var fn = task.fn
|
||||
currentTestError = task.err
|
||||
var timeout = 0, delay = defaultDelay, s = new Date
|
||||
var current = cursor
|
||||
var arg
|
||||
|
||||
globalTimeout = setDelay
|
||||
|
||||
var fn = fns[cursor++]
|
||||
var timeout = 0, delay = 200, s = new Date
|
||||
var isDone = false
|
||||
|
||||
// public API, may only be called once from use code (or after returned Promise resolution)
|
||||
function done(err) {
|
||||
if (err) {
|
||||
if (err.message) record(err.message, err)
|
||||
else record(err)
|
||||
subjects.pop()
|
||||
next()
|
||||
}
|
||||
if (timeout !== undefined) {
|
||||
timeout = clearTimeout(timeout)
|
||||
if (delay !== Infinity) record(null)
|
||||
if (!isDone) next()
|
||||
else throw new Error("`" + arg + "()` should only be called once")
|
||||
isDone = true
|
||||
}
|
||||
else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms")
|
||||
if (!isDone) isDone = true
|
||||
else throw new Error("`" + arg + "()` should only be called once")
|
||||
if (timeout === undefined) console.warn("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms\n" + o.cleanStackTrace(task.err))
|
||||
finalizeAsync(err)
|
||||
}
|
||||
// for internal use only
|
||||
function finalizeAsync(err) {
|
||||
if (err == null) {
|
||||
if (task.err != null) succeed(new Assert)
|
||||
} else {
|
||||
if (err instanceof Error) fail(new Assert, err.message, err)
|
||||
else fail(new Assert, String(err), null)
|
||||
}
|
||||
if (timeout !== undefined) timeout = clearTimeout(timeout)
|
||||
if (current === cursor) next()
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
timeout = setTimeout(function() {
|
||||
timeout = undefined
|
||||
record("async test timed out")
|
||||
next()
|
||||
finalizeAsync("async test timed out after " + delay + "ms")
|
||||
}, Math.min(delay, 2147483647))
|
||||
}
|
||||
|
||||
function setDelay (t) {
|
||||
if (typeof t !== "number") throw new Error("timeout() and o.timeout() expect a number as argument")
|
||||
delay = t
|
||||
}
|
||||
if (fn.length > 0) {
|
||||
var body = fn.toString()
|
||||
var arg = (body.match(/\(([\w$]+)/) || body.match(/([\w$]+)\s*=>/) || []).pop()
|
||||
if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once")
|
||||
arg = (body.match(/^(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*=>/) || body.match(/\((?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*[,\)]/) || []).pop()
|
||||
if (body.indexOf(arg) === body.lastIndexOf(arg)) {
|
||||
var e = new Error
|
||||
e.stack = "`" + arg + "()` should be called at least once\n" + o.cleanStackTrace(task.err)
|
||||
throw e
|
||||
}
|
||||
try {
|
||||
fn(done, function(t) {delay = t})
|
||||
fn(done, setDelay)
|
||||
}
|
||||
catch (e) {
|
||||
done(e)
|
||||
if (task.err != null) finalizeAsync(e)
|
||||
// The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown.
|
||||
else throw e
|
||||
}
|
||||
if (timeout === 0) {
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
else {
|
||||
var p = fn()
|
||||
if (p && p.then) {
|
||||
startTimer()
|
||||
p.then(function() { done() }, done)
|
||||
} else {
|
||||
nextTickish(next)
|
||||
} else {
|
||||
try{
|
||||
var p = fn()
|
||||
if (p && p.then) {
|
||||
startTimer()
|
||||
p.then(function() { done() }, done)
|
||||
} else {
|
||||
nextTickish(next)
|
||||
}
|
||||
} catch (e) {
|
||||
if (task.err != null) finalizeAsync(e)
|
||||
// The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown.
|
||||
else throw e
|
||||
}
|
||||
}
|
||||
globalTimeout = noTimeoutRightNow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -178,7 +212,7 @@ else window.o = m()
|
|||
function hook(name) {
|
||||
return function(predicate) {
|
||||
if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate)
|
||||
ctx[name] = predicate
|
||||
ctx[name] = new Task(predicate, ensureStackTrace(new Error))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +229,7 @@ else window.o = m()
|
|||
}
|
||||
function deepEqual(a, b) {
|
||||
if (a === b) return true
|
||||
if (a === null ^ b === null || a === undefined ^ b === undefined) return false
|
||||
if (a === null ^ b === null || a === undefined ^ b === undefined) return false // eslint-disable-line no-bitwise
|
||||
if (typeof a === "object" && typeof b === "object") {
|
||||
var aIsArgs = isArguments(a), bIsArgs = isArguments(b)
|
||||
if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) {
|
||||
|
|
@ -227,54 +261,99 @@ else window.o = m()
|
|||
return false
|
||||
}
|
||||
|
||||
function Assert(value) {this.value = value}
|
||||
function isRunning() {return results != null}
|
||||
function Assert(value) {
|
||||
this.value = value
|
||||
this.i = results.length
|
||||
results.push({pass: null, context: "", message: "Incomplete assertion in the test definition starting at...", error: currentTestError, testError: currentTestError})
|
||||
}
|
||||
function Task(fn, err) {
|
||||
this.fn = fn
|
||||
this.err = err
|
||||
}
|
||||
function define(name, verb, compare) {
|
||||
Assert.prototype[name] = function assert(value) {
|
||||
if (compare(this.value, value)) record(null)
|
||||
else record(serialize(this.value) + "\n" + verb + "\n" + serialize(value))
|
||||
if (compare(this.value, value)) succeed(this)
|
||||
else fail(this, serialize(this.value) + "\n " + verb + "\n" + serialize(value))
|
||||
var self = this
|
||||
return function(message) {
|
||||
var result = results[results.length - 1]
|
||||
result.message = message + "\n\n" + result.message
|
||||
if (!self.pass) self.message = message + "\n\n" + self.message
|
||||
}
|
||||
}
|
||||
}
|
||||
function record(message, error) {
|
||||
var result = {pass: message === null}
|
||||
if (result.pass === false) {
|
||||
if (error == null) {
|
||||
error = new Error
|
||||
if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}}
|
||||
}
|
||||
result.context = subjects.join(" > ")
|
||||
result.message = message
|
||||
result.error = error
|
||||
|
||||
}
|
||||
results.push(result)
|
||||
function succeed(assertion) {
|
||||
results[assertion.i].pass = true
|
||||
}
|
||||
function fail(assertion, message, error) {
|
||||
results[assertion.i].pass = false
|
||||
results[assertion.i].context = subjects.join(" > ")
|
||||
results[assertion.i].message = message
|
||||
results[assertion.i].error = error != null ? error : ensureStackTrace(new Error)
|
||||
}
|
||||
function serialize(value) {
|
||||
if (hasProcess) return require("util").inspect(value)
|
||||
if (hasProcess) return require("util").inspect(value) // eslint-disable-line global-require
|
||||
if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value)
|
||||
else if (typeof value === "function") return value.name || "<anonymous function>"
|
||||
try {return JSON.stringify(value)} catch (e) {return String(value)}
|
||||
}
|
||||
function highlight(message) {
|
||||
return hasProcess ? "\x1b[31m" + message + "\x1b[0m" : "%c" + message + "%c "
|
||||
function noTimeoutRightNow() {
|
||||
throw new Error("o.timeout must be called snchronously from within a test definition or a hook")
|
||||
}
|
||||
var colorCodes = {
|
||||
red: "31m",
|
||||
red2: "31;1m",
|
||||
green: "32;1m"
|
||||
}
|
||||
function highlight(message, color) {
|
||||
var code = colorCodes[color] || colorCodes.red;
|
||||
return hasProcess ? (process.stdout.isTTY ? "\x1b[" + code + message + "\x1b[0m" : message) : "%c" + message + "%c "
|
||||
}
|
||||
function cStyle(color, bold) {
|
||||
return hasProcess||!color ? "" : "color:"+color+(bold ? ";font-weight:bold" : "")
|
||||
}
|
||||
function ensureStackTrace(error) {
|
||||
// mandatory to get a stack in IE 10 and 11 (and maybe other envs?)
|
||||
if (error.stack === undefined) try { throw error } catch(e) {return e}
|
||||
else return error
|
||||
}
|
||||
function getStackName(e, exp) {
|
||||
return e.stack && exp.test(e.stack) ? e.stack.match(exp)[1] : null
|
||||
}
|
||||
|
||||
o.report = function (results) {
|
||||
var errCount = 0
|
||||
for (var i = 0, r; r = results[i]; i++) {
|
||||
if (r.pass == null) {
|
||||
r.testError.stack = r.message + "\n" + o.cleanStackTrace(r.testError)
|
||||
r.testError.message = r.message
|
||||
throw r.testError
|
||||
}
|
||||
if (!r.pass) {
|
||||
var stackTrace = o.cleanStackTrace(r.error)
|
||||
console.error(r.context + ":\n" + highlight(r.message) + (stackTrace ? "\n\n" + stackTrace + "\n\n" : ""), hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
|
||||
var couldHaveABetterStackTrace = !stackTrace || timeoutStackName != null && stackTrace.indexOf(timeoutStackName) !== -1
|
||||
if (couldHaveABetterStackTrace) stackTrace = r.testError != null ? o.cleanStackTrace(r.testError) : r.error.stack || ""
|
||||
console.error(
|
||||
(hasProcess ? "\n" : "") +
|
||||
highlight(r.context + ":", "red2") + "\n" +
|
||||
highlight(r.message, "red") +
|
||||
(stackTrace ? "\n" + stackTrace + "\n" : ""),
|
||||
|
||||
cStyle("black", true), "", // reset to default
|
||||
cStyle("red"), cStyle("black")
|
||||
)
|
||||
errCount++
|
||||
}
|
||||
}
|
||||
var pl = results.length === 1 ? "" : "s"
|
||||
var resultSummary = (errCount === 0) ?
|
||||
highlight((pl ? "All " : "The ") + results.length + " assertion" + pl + " passed", "green"):
|
||||
highlight(errCount + " out of " + results.length + " assertion" + pl + " failed", "red2")
|
||||
var runningTime = " in " + Math.round(Date.now() - start) + "ms"
|
||||
|
||||
console.log(
|
||||
(name ? name + ": " : "") +
|
||||
results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
|
||||
"of which " + results.filter(function(result){return result.error}).length + " failed"
|
||||
(hasProcess ? "––––––\n" : "") +
|
||||
(name ? name + ": " : "") + resultSummary + runningTime,
|
||||
cStyle((errCount === 0 ? "green" : "red"), true), ""
|
||||
)
|
||||
return errCount
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ospec",
|
||||
"version": "1.4.0",
|
||||
"version": "3.0.1",
|
||||
"description": "Noiseless testing framework",
|
||||
"main": "ospec.js",
|
||||
"directories": {
|
||||
|
|
@ -12,5 +12,8 @@
|
|||
"bin": {
|
||||
"ospec": "./bin/ospec"
|
||||
},
|
||||
"repository": "MithrilJS/mithril.js"
|
||||
"repository": "MithrilJS/mithril.js",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,137 @@
|
|||
var callAsync = require("../../test-utils/callAsync")
|
||||
var o = require("../ospec")
|
||||
|
||||
new function(o) {
|
||||
o = o.new()
|
||||
|
||||
o.spec("ospec", function() {
|
||||
o("skipped", function() {
|
||||
// this throws an async error that can't be caught in browsers
|
||||
if (typeof process !== "undefined") {
|
||||
o("incomplete assertion", function(done) {
|
||||
var stackMatcher = /([\w\.\\\/\-]+):(\d+):/
|
||||
// /!\ this test relies on the `new Error` expression being six lines
|
||||
// above the `oo("test", function(){...})` call.
|
||||
var matches = (new Error).stack.match(stackMatcher)
|
||||
if (matches != null) {
|
||||
var name = matches[1]
|
||||
var num = Number(matches[2])
|
||||
}
|
||||
var oo = o.new()
|
||||
oo("test", function() {
|
||||
oo("incomplete")
|
||||
})
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].message).equals("Incomplete assertion in the test definition starting at...")
|
||||
o(results[0].pass).equals(null)
|
||||
var stack = o.cleanStackTrace(results[0].testError)
|
||||
var matches2 = stack && stack.match(stackMatcher)
|
||||
if (matches != null && matches2 != null) {
|
||||
o(matches[1]).equals(name)
|
||||
o(Number(matches2[2])).equals(num + 6)
|
||||
}
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
o("o.only", function(done) {
|
||||
var oo = o.new()
|
||||
|
||||
oo.spec("won't run", function() {
|
||||
oo("nope, skipped", function() {
|
||||
o(true).equals(false)
|
||||
})
|
||||
o.only(".only()", function() {
|
||||
o(2).equals(2)
|
||||
})
|
||||
|
||||
oo.spec("ospec", function() {
|
||||
oo("skipped as well", function() {
|
||||
oo(true).equals(false)
|
||||
})
|
||||
oo.only(".only()", function() {
|
||||
oo(2).equals(2)
|
||||
}, true)
|
||||
oo.only("another .only()", function(done) {
|
||||
done("that fails")
|
||||
}, true)
|
||||
})
|
||||
|
||||
o.run()
|
||||
}(o)
|
||||
oo.run(function(results){
|
||||
o(results.length).equals(2)
|
||||
o(results[0].pass).equals(true)
|
||||
o(results[1].pass).equals(false)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// Predicate test passing on clone results
|
||||
o.spec("reporting", function() {
|
||||
var oo
|
||||
o.beforeEach(function(){
|
||||
oo = o.new()
|
||||
|
||||
oo.spec("clone", function() {
|
||||
oo("fail", function() {
|
||||
oo(true).equals(false)
|
||||
})
|
||||
|
||||
oo("pass", function() {
|
||||
oo(true).equals(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
o("reports per instance", function(done, timeout) {
|
||||
timeout(100) // Waiting on clone
|
||||
|
||||
oo.run(function(results) {
|
||||
o(typeof results).equals("object")
|
||||
o("length" in results).equals(true)
|
||||
o(results.length).equals(2)("Two results")
|
||||
|
||||
o("error" in results[0] && "pass" in results[0]).equals(true)("error and pass keys present in failing result")
|
||||
o(results[0].pass).equals(false)("Test meant to fail has failed")
|
||||
o(results[1].pass).equals(true)("Test meant to pass has passed")
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("o.report() returns the number of failures", function () {
|
||||
var log = console.log, error = console.error
|
||||
console.log = o.spy()
|
||||
console.error = o.spy()
|
||||
|
||||
function makeError(msg) {try{throw msg ? new Error(msg) : new Error} catch(e){return e}}
|
||||
try {
|
||||
var errCount = o.report([{pass: true}, {pass: true}])
|
||||
|
||||
o(errCount).equals(0)
|
||||
o(console.log.callCount).equals(1)
|
||||
o(console.error.callCount).equals(0)
|
||||
|
||||
errCount = o.report([
|
||||
{pass: false, error: makeError("hey"), message: "hey"}
|
||||
])
|
||||
|
||||
o(errCount).equals(1)
|
||||
o(console.log.callCount).equals(2)
|
||||
o(console.error.callCount).equals(1)
|
||||
|
||||
errCount = o.report([
|
||||
{pass: false, error: makeError("hey"), message: "hey"},
|
||||
{pass: true},
|
||||
{pass: false, error: makeError("ho"), message: "ho"}
|
||||
])
|
||||
|
||||
o(errCount).equals(2)
|
||||
o(console.log.callCount).equals(3)
|
||||
o(console.error.callCount).equals(3)
|
||||
} catch (e) {
|
||||
o(1).equals(0)("Error while testing the reporter")
|
||||
}
|
||||
|
||||
console.log = log
|
||||
console.error = error
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
new function(o) {
|
||||
var clone = o.new()
|
||||
|
|
@ -75,7 +192,7 @@ new function(o) {
|
|||
{pass: true},
|
||||
{pass: false, error: makeError("ho"), message: "ho"}
|
||||
])
|
||||
|
||||
|
||||
o(errCount).equals(2)
|
||||
o(console.log.callCount).equals(3)
|
||||
o(console.error.callCount).equals(3)
|
||||
|
|
@ -92,6 +209,7 @@ new function(o) {
|
|||
o.spec("ospec", function() {
|
||||
o.spec("sync", function() {
|
||||
var a = 0, b = 0, illegalAssertionThrows = false
|
||||
var reservedTestNameTrows = false
|
||||
|
||||
o.before(function() {a = 1})
|
||||
o.after(function() {a = 0})
|
||||
|
|
@ -100,6 +218,7 @@ o.spec("ospec", function() {
|
|||
o.afterEach(function() {b = 0})
|
||||
|
||||
try {o("illegal assertion")} catch (e) {illegalAssertionThrows = true}
|
||||
try {o("\x01reserved test name", function(){})} catch (e) {reservedTestNameTrows = true}
|
||||
|
||||
o("assertions", function() {
|
||||
var nestedTestDeclarationThrows = false
|
||||
|
|
@ -107,6 +226,7 @@ o.spec("ospec", function() {
|
|||
|
||||
o(illegalAssertionThrows).equals(true)
|
||||
o(nestedTestDeclarationThrows).equals(true)
|
||||
o(reservedTestNameTrows).equals(true)
|
||||
|
||||
var spy = o.spy()
|
||||
spy(a)
|
||||
|
|
@ -157,6 +277,8 @@ o.spec("ospec", function() {
|
|||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(1)
|
||||
o(spy.args[0]).equals(1)
|
||||
o(spy.calls.length).equals(1)
|
||||
o(spy.calls[0]).deepEquals({this: undefined, args: [1]})
|
||||
})
|
||||
o("spy wrapping", function() {
|
||||
var spy = o.spy(function view(vnode){
|
||||
|
|
@ -174,47 +296,368 @@ o.spec("ospec", function() {
|
|||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(1)
|
||||
o(spy.args[0]).deepEquals({children: children})
|
||||
o(spy.calls.length).equals(1)
|
||||
o(spy.calls[0]).deepEquals({this: state, args: [{children: children}]})
|
||||
o(state).deepEquals({drawn: true})
|
||||
o(output).deepEquals({tag: "div", children: children})
|
||||
})
|
||||
})
|
||||
o.spec("async callback", function() {
|
||||
var a = 0, b = 0
|
||||
o.after(function() {
|
||||
o(a).equals(0)
|
||||
o(b).equals(0)
|
||||
})
|
||||
o.spec("", function(){
|
||||
o.before(function(done) {
|
||||
callAsync(function() {
|
||||
a = 1
|
||||
done()
|
||||
})
|
||||
})
|
||||
o.after(function(done) {
|
||||
callAsync(function() {
|
||||
a = 0
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o.beforeEach(function(done) {
|
||||
o(b).equals(0)
|
||||
callAsync(function() {
|
||||
b = 1
|
||||
done()
|
||||
})
|
||||
})
|
||||
o.afterEach(function(done) {
|
||||
callAsync(function() {
|
||||
b = 0
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("hooks work as intended the first time", function(done) {
|
||||
callAsync(function() {
|
||||
var spy = o.spy()
|
||||
spy(a)
|
||||
|
||||
o(a).equals(1)
|
||||
o(b).equals(1)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("hooks work as intended the second time", function(done) {
|
||||
callAsync(function() {
|
||||
var spy = o.spy()
|
||||
spy(a)
|
||||
|
||||
o(a).equals(1)
|
||||
o(b).equals(1)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o.spec("throwing in test context is recoreded as a failure", function() {
|
||||
var oo
|
||||
o.beforeEach(function(){oo = o.new()})
|
||||
o.afterEach(function() {
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].pass).equals(false)
|
||||
})
|
||||
})
|
||||
o("sync test", function() {
|
||||
oo("throw in sync test", function() {throw new Error})
|
||||
})
|
||||
o("async test", function() {
|
||||
oo("throw in async test", function(done) {
|
||||
throw new Error
|
||||
done() // eslint-disable-line no-unreachable
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o.spec("timeout", function () {
|
||||
o("when using done()", function(done) {
|
||||
var oo = o.new()
|
||||
var err
|
||||
// the success of this test is dependent on having the
|
||||
// oo() call three linew below this one
|
||||
try {throw new Error} catch(e) {err = e}
|
||||
if (err.stack) {
|
||||
var line = Number(err.stack.match(/:(\d+):/)[1])
|
||||
oo("", function(oodone, timeout) {
|
||||
// oodone() keep this line for now
|
||||
timeout(1)
|
||||
})
|
||||
oo.run((function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].pass).equals(false)
|
||||
// todo test cleaned up results[0].error stack trace for the presence
|
||||
// of the timeout stack entry
|
||||
o(results[0].testError instanceof Error).equals(true)
|
||||
o(o.cleanStackTrace(results[0].testError).indexOf("test-ospec.js:" + (line + 3) + ":")).notEquals(-1)
|
||||
|
||||
done()
|
||||
}))
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
})
|
||||
o("when using a thenable", function(done) {
|
||||
var oo = o.new()
|
||||
var err
|
||||
// the success of this test is dependent on having the
|
||||
// oo() call three linew below this one
|
||||
try {throw new Error} catch(e) {err = e}
|
||||
if (err.stack) {
|
||||
var line = Number(err.stack.match(/:(\d+):/)[1])
|
||||
oo("", function() {
|
||||
oo.timeout(1)
|
||||
return {then: function(){}}
|
||||
})
|
||||
oo.run((function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].pass).equals(false)
|
||||
o(results[0].testError instanceof Error).equals(true)
|
||||
o(o.cleanStackTrace(results[0].testError).indexOf("test-ospec.js:" + (line + 3) + ":")).notEquals(-1)
|
||||
|
||||
done()
|
||||
}))
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
o.spec("o.timeout", function() {
|
||||
o("throws when called out of test definitions", function(done) {
|
||||
var oo = o.new()
|
||||
var count = 0
|
||||
try { oo.timeout(1) } catch (e) { count++ }
|
||||
oo.spec("a spec", function() {
|
||||
try { oo.timeout(1) } catch (e) { count++ }
|
||||
})
|
||||
oo("", function() {
|
||||
oo.timeout(30)
|
||||
return {then: function(f) {setTimeout(f)}}
|
||||
})
|
||||
oo.run(function(){
|
||||
o(count).equals(2)
|
||||
|
||||
o.before(function(done) {
|
||||
callAsync(function() {
|
||||
a = 1
|
||||
done()
|
||||
})
|
||||
})
|
||||
o.after(function(done) {
|
||||
callAsync(function() {
|
||||
a = 0
|
||||
o("works", function(done) {
|
||||
var oo = o.new()
|
||||
var t = new Date
|
||||
oo("", function() {
|
||||
oo.timeout(10)
|
||||
return {then: function() {}}
|
||||
})
|
||||
oo.run(function(){
|
||||
o(new Date - t >= 10).equals(true)
|
||||
o(200 > new Date - t).equals(true)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("o.specTimeout", function() {
|
||||
o("throws when called inside of test definitions", function(done) {
|
||||
var err
|
||||
var oo = o.new()
|
||||
oo("", function() {
|
||||
try { oo.specTimeout(5) } catch (e) {err = e}
|
||||
return {then: function(f) {setTimeout(f)}}
|
||||
})
|
||||
oo.run(function(){
|
||||
o(err instanceof Error).equals(true)
|
||||
|
||||
o.beforeEach(function(done) {
|
||||
callAsync(function() {
|
||||
b = 1
|
||||
done()
|
||||
})
|
||||
})
|
||||
o.afterEach(function(done) {
|
||||
callAsync(function() {
|
||||
b = 0
|
||||
o("works", function(done) {
|
||||
var oo = o.new()
|
||||
var t
|
||||
|
||||
oo.specTimeout(10)
|
||||
oo.beforeEach(function () {
|
||||
t = new Date
|
||||
})
|
||||
oo.afterEach(function () {
|
||||
var diff = new Date - t
|
||||
o(diff >= 10).equals(true)
|
||||
o(diff < 200).equals(true)
|
||||
})
|
||||
|
||||
oo("", function() {
|
||||
oo(true).equals(true)
|
||||
|
||||
return {then: function() {}}
|
||||
})
|
||||
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(2)
|
||||
o(results[0].pass).equals(true)
|
||||
o(results[1].pass).equals(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("The parent and sibling suites are not affected by the specTimeout", function(done) {
|
||||
var oo = o.new()
|
||||
var t
|
||||
|
||||
o("async hooks", function(done) {
|
||||
callAsync(function() {
|
||||
var spy = o.spy()
|
||||
spy(a)
|
||||
oo.specTimeout(50)
|
||||
oo.beforeEach(function () {
|
||||
t = new Date
|
||||
})
|
||||
oo.afterEach(function () {
|
||||
var diff = new Date - t
|
||||
o(diff >= 50).equals(true)
|
||||
o(diff < 80).equals(true)
|
||||
})
|
||||
|
||||
o(a).equals(b)
|
||||
o(a).equals(1)("a and b should be initialized")
|
||||
oo.spec("nested 1", function () {
|
||||
oo.specTimeout(80)
|
||||
})
|
||||
|
||||
oo("", function() {
|
||||
oo(true).equals(true)
|
||||
|
||||
return {then: function() {}}
|
||||
})
|
||||
oo.spec("nested 2", function () {
|
||||
oo.specTimeout(80)
|
||||
})
|
||||
oo.spec("nested 3", function () {
|
||||
oo("", function() {
|
||||
oo(true).equals(true)
|
||||
|
||||
return {then: function() {}}
|
||||
})
|
||||
})
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(4)
|
||||
o(results[0].pass).equals(true)
|
||||
o(results[1].pass).equals(false)
|
||||
o(results[2].pass).equals(true)
|
||||
o(results[3].pass).equals(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("nested suites inherit the specTimeout", function(done) {
|
||||
var oo = o.new()
|
||||
|
||||
oo.specTimeout(50)
|
||||
oo.spec("nested", function () {
|
||||
oo.spec("deeply", function() {
|
||||
var t
|
||||
|
||||
oo.beforeEach(function () {
|
||||
t = new Date
|
||||
})
|
||||
oo.afterEach(function () {
|
||||
var diff = new Date - t
|
||||
o(diff >= 50).equals(true)
|
||||
o(diff < 80).equals(true)
|
||||
})
|
||||
|
||||
oo("", function() {
|
||||
oo(true).equals(true)
|
||||
|
||||
return {then: function() {}}
|
||||
})
|
||||
})
|
||||
})
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(2)
|
||||
o(results[0].pass).equals(true)
|
||||
o(results[1].pass).equals(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o.spec("calling done() twice throws", function () {
|
||||
o("two successes", function(done) {
|
||||
var oo = o.new()
|
||||
var err = null
|
||||
oo("foo", function(oodone) {
|
||||
try {
|
||||
oodone()
|
||||
oodone()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
o(err instanceof Error).equals(true)
|
||||
o(err.message).equals("`oodone()` should only be called once")
|
||||
})
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].pass).equals(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("a success followed by an error", function(done) {
|
||||
var oo = o.new()
|
||||
var err = null
|
||||
oo("foo", function(oodone) {
|
||||
try {
|
||||
oodone()
|
||||
oodone("error")
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
o(err instanceof Error).equals(true)
|
||||
o(err.message).equals("`oodone()` should only be called once")
|
||||
})
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].pass).equals(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("two errors", function(done) {
|
||||
var oo = o.new()
|
||||
var err = null
|
||||
oo("foo", function(oodone) {
|
||||
try {
|
||||
oodone("bar")
|
||||
oodone("baz")
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
o(err instanceof Error).equals(true)
|
||||
o(err.message).equals("`oodone()` should only be called once")
|
||||
})
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].pass).equals(false)
|
||||
o(results[0].message).equals("bar")
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("an error followed by a success", function(done) {
|
||||
var oo = o.new()
|
||||
var err = null
|
||||
oo("foo", function(oodone) {
|
||||
try {
|
||||
oodone("bar")
|
||||
oodone()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
o(err instanceof Error).equals(true)
|
||||
o(err.message).equals("`oodone()` should only be called once")
|
||||
})
|
||||
oo.run(function(results) {
|
||||
o(results.length).equals(1)
|
||||
o(results[0].pass).equals(false)
|
||||
o(results[0].message).equals("bar")
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
|
@ -227,7 +670,7 @@ o.spec("ospec", function() {
|
|||
} catch(error) {
|
||||
var trace = o.cleanStackTrace(error)
|
||||
o(trace).notEquals("break")
|
||||
o(trace.includes("test-ospec.js")).equals(true)
|
||||
o(trace.indexOf("test-ospec.js") !== -1).equals(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -279,3 +722,79 @@ o.spec("ospec", function() {
|
|||
})
|
||||
})
|
||||
})
|
||||
o.spec("the done parser", function() {
|
||||
o("accepts non-English names", function() {
|
||||
var oo = o.new()
|
||||
var threw = false
|
||||
oo("test", function(完了) {
|
||||
oo(true).equals(true)
|
||||
完了()
|
||||
})
|
||||
try {oo.run(function(){})} catch(e) {threw = true}
|
||||
o(threw).equals(false)
|
||||
})
|
||||
o("tolerates comments", function() {
|
||||
var oo = o.new()
|
||||
var threw = false
|
||||
oo("test", function(/*hey
|
||||
*/ /**/ //ho
|
||||
done /*hey
|
||||
*/ /**/ //huuu
|
||||
, timeout
|
||||
) {
|
||||
timeout(5)
|
||||
oo(true).equals(true)
|
||||
done()
|
||||
})
|
||||
try {oo.run(function(){})} catch(e) {threw = true}
|
||||
o(threw).equals(false)
|
||||
})
|
||||
/*eslint-disable no-eval*/
|
||||
try {eval("(()=>{})()"); o.spec("with ES6 arrow functions", function() {
|
||||
function getCommentContent(f) {
|
||||
f = f.toString()
|
||||
return f.slice(f.indexOf("/*") + 2, f.lastIndexOf("*/"))
|
||||
}
|
||||
o("has no false positives 1", function(){
|
||||
var oo = o.new()
|
||||
var threw = false
|
||||
eval(getCommentContent(function(){/*
|
||||
oo(
|
||||
'Async test parser mistakenly identified 1st token after a parens to be `done` reference',
|
||||
done => {
|
||||
oo(threw).equals(false)
|
||||
done()
|
||||
}
|
||||
)
|
||||
*/}))
|
||||
try {oo.run(function(){})} catch(e) {threw = true}
|
||||
o(threw).equals(false)
|
||||
})
|
||||
o("has no false negatives", function(){
|
||||
var oo = o.new()
|
||||
var threw = false
|
||||
eval(getCommentContent(function(){/*
|
||||
oo(
|
||||
"Multiple references to the wrong thing doesn't fool the checker",
|
||||
done => {
|
||||
oo(threw).equals(false)
|
||||
oo(threw).equals(false)
|
||||
}
|
||||
)
|
||||
*/}))
|
||||
try {oo.run(function(){})} catch(e) {threw = true}
|
||||
o(threw).equals(true)
|
||||
})
|
||||
o("isn't fooled by comments", function(){
|
||||
var oo = o.new()
|
||||
var threw = false
|
||||
oo(
|
||||
"comments won't throw the parser off",
|
||||
eval("done /*hey*/ /**/ => {oo(threw).equals(false);done()}")
|
||||
)
|
||||
try {oo.run(function(){})} catch(e) {threw = true}
|
||||
o(threw).equals(false)
|
||||
})
|
||||
})} catch (e) {/*ES5 env, or no eval, ignore*/}
|
||||
/*eslint-enable no-eval*/
|
||||
})
|
||||
|
|
|
|||
3540
package-lock.json
generated
3540
package-lock.json
generated
File diff suppressed because it is too large
Load diff
23
package.json
23
package.json
|
|
@ -7,10 +7,11 @@
|
|||
"main": "mithril.js",
|
||||
"repository": "MithrilJS/mithril.js",
|
||||
"scripts": {
|
||||
"dev": "node bundler/cli browser.js -o mithril.js -w",
|
||||
"dev": "node bundler/cli browser.js -output mithril.js -watch",
|
||||
"build": "npm run build-browser & npm run build-min",
|
||||
"build-browser": "node bundler/cli browser.js -o mithril.js",
|
||||
"build-min": "node bundler/cli browser.js -o mithril.min.js -m",
|
||||
"build-browser": "node bundler/cli browser.js -output mithril.js",
|
||||
"build-min": "node bundler/cli browser.js -output mithril.min.js -minify",
|
||||
"precommit": "lint-staged",
|
||||
"lintdocs": "node docs/lint",
|
||||
"gendocs": "node docs/generate",
|
||||
"lint": "eslint . || true",
|
||||
|
|
@ -27,14 +28,24 @@
|
|||
"devDependencies": {
|
||||
"@alrra/travis-scripts": "^3.0.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"dedent": "^0.7.0",
|
||||
"eslint": "^3.19.0",
|
||||
"gh-pages": "^0.12.0",
|
||||
"glob": "^7.1.2",
|
||||
"istanbul": "^0.4.5",
|
||||
"marked": "^0.3.6"
|
||||
"lint-staged": "^4.0.4",
|
||||
"locater": "^1.3.0",
|
||||
"marked": "^0.3.19",
|
||||
"pinpoint": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"ospec": "./ospec/bin/ospec",
|
||||
"bundle": "./bundler/bin/bundle"
|
||||
"ospec": "./ospec/bin/ospec"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"eslint . --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@
|
|||
<script src="../test-utils/pushStateMock.js"></script>
|
||||
<script src="../test-utils/xhrMock.js"></script>
|
||||
<script src="../test-utils/browserMock.js"></script>
|
||||
<script src="../mithril.js"></script>
|
||||
<script src="../render/vnode.js"></script>
|
||||
<script src="../render/render.js"></script>
|
||||
<script src="../render/hyperscript.js"></script>
|
||||
<script src="../node_modules/lodash/lodash.js"></script>
|
||||
<script src="../node_modules/benchmark/benchmark.js"></script>
|
||||
<script src="test-perf.js"></script>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ var browserMock = require("../test-utils/browserMock")
|
|||
// Do this silly dance so browser testing works
|
||||
var B = typeof Benchmark === "undefined" ? require("benchmark") : Benchmark
|
||||
|
||||
var m, scratch;
|
||||
var scratch;
|
||||
|
||||
// set up browser env on before running tests
|
||||
var doc = typeof document !== "undefined" ? document : null
|
||||
|
|
@ -43,15 +43,20 @@ if(!doc) {
|
|||
doc = mock.document
|
||||
}
|
||||
|
||||
// Have to include mithril AFTER browser polyfill is set up
|
||||
m = require("../mithril") // eslint-disable-line global-require
|
||||
var m = require("../render/hyperscript")
|
||||
m.render = require("../render/render")(window).render
|
||||
|
||||
scratch = doc.createElement("div");
|
||||
|
||||
(doc.body || doc.documentElement).appendChild(scratch)
|
||||
function resetScratch() {
|
||||
doc.documentElement.innerHTML = "<div></div>"
|
||||
scratch = doc.documentElement.firstChild
|
||||
}
|
||||
|
||||
resetScratch()
|
||||
|
||||
// Initialize benchmark suite
|
||||
var suite = new B.Suite("mithril perf")
|
||||
var xuite = {add: function(options) {console.log("skipping " + options.name)}} // eslint-disable-line no-unused-vars
|
||||
|
||||
suite.on("start", function() {
|
||||
this.start = Date.now();
|
||||
|
|
@ -60,7 +65,7 @@ suite.on("start", function() {
|
|||
suite.on("cycle", function(e) {
|
||||
console.log(e.target.toString())
|
||||
|
||||
scratch.innerHTML = ""
|
||||
resetScratch()
|
||||
})
|
||||
|
||||
suite.on("complete", function() {
|
||||
|
|
@ -70,7 +75,7 @@ suite.on("complete", function() {
|
|||
suite.on("error", console.error.bind(console))
|
||||
|
||||
suite.add({
|
||||
name : "rerender without changes",
|
||||
name : "rerender identical vnode",
|
||||
onStart : function() {
|
||||
this.vdom = m("div", {class: "foo bar", "data-foo": "bar", p: 2},
|
||||
m("header",
|
||||
|
|
@ -119,6 +124,53 @@ suite.add({
|
|||
}
|
||||
})
|
||||
|
||||
suite.add({
|
||||
name : "rerender same tree",
|
||||
fn : function() {
|
||||
m.render(scratch, m("div", {class: "foo bar", "data-foo": "bar", p: 2},
|
||||
m("header",
|
||||
m("h1", {class: "asdf"}, "a ", "b", " c ", 0, " d"),
|
||||
m("nav",
|
||||
m("a", {href: "/foo"}, "Foo"),
|
||||
m("a", {href: "/bar"}, "Bar")
|
||||
)
|
||||
),
|
||||
m("main",
|
||||
m("form", {onSubmit: function onSubmit() {}},
|
||||
m("input", {type: "checkbox", checked: true}),
|
||||
m("input", {type: "checkbox", checked: false}),
|
||||
m("fieldset",
|
||||
m("label",
|
||||
m("input", {type: "radio", checked: true})
|
||||
),
|
||||
m("label",
|
||||
m("input", {type: "radio"})
|
||||
)
|
||||
),
|
||||
m("button-bar",
|
||||
m("button",
|
||||
{style: "width:10px; height:10px; border:1px solid #FFF;"},
|
||||
"Normal CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: "top:0 ; right: 20"},
|
||||
"Poor CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: "invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;", icon: true},
|
||||
"Poorer CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: {margin: 0, padding: "10px", overflow: "visible"}},
|
||||
"Object CSS"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
suite.add({
|
||||
name : "construct large VDOM tree",
|
||||
|
||||
|
|
@ -181,12 +233,12 @@ suite.add({
|
|||
|
||||
suite.add({
|
||||
name : "mutate styles/properties",
|
||||
|
||||
// minSamples: 100,
|
||||
onStart : function () {
|
||||
var counter = 0
|
||||
var keyLooper = function (n) { return function (c) { return c % n ? (c + "px") : c } }
|
||||
var get = function (obj, i) { return obj[i%obj.length] }
|
||||
var classes = ["foo", "foo bar", "", "baz-bat", null, "fooga"]
|
||||
var classes = ["foo", "foo bar", "", "baz-bat", null, "fooga", null, null, undefined]
|
||||
var styles = []
|
||||
var multivalue = ["0 1px", "0 0 1px 0", "0", "1px", "20px 10px", "7em 5px", "1px 0 5em 2px"]
|
||||
var stylekeys = [
|
||||
|
|
@ -212,21 +264,26 @@ suite.add({
|
|||
|
||||
this.count = 0
|
||||
this.app = function (index) {
|
||||
return m("div",
|
||||
{
|
||||
class: get(classes, index),
|
||||
"data-index": index,
|
||||
title: index.toString(36)
|
||||
},
|
||||
m("input", {type: "checkbox", checked: index % 3 == 0}),
|
||||
m("input", {value: "test " + (Math.floor(index / 4)), disabled: index % 10 ? null : true}),
|
||||
m("div", {class: get(classes, index * 11)},
|
||||
m("p", {style: get(styles, index)}, "p1"),
|
||||
m("p", {style: get(styles, index + 1)}, "p2"),
|
||||
m("p", {style: get(styles, index * 2)}, "p3"),
|
||||
m("p", {style: get(styles, index * 3 + 1)}, "p4")
|
||||
var last = index + 300
|
||||
var vnodes = []
|
||||
for (; index < last; index++) vnodes.push(
|
||||
m("div.booga",
|
||||
{
|
||||
class: get(classes, index),
|
||||
"data-index": index,
|
||||
title: index.toString(36)
|
||||
},
|
||||
m("input.dooga", {type: "checkbox", checked: index % 3 == 0}),
|
||||
m("input", {value: "test " + (Math.floor(index / 4)), disabled: index % 10 ? null : true}),
|
||||
m("div", {class: get(classes, index * 11)},
|
||||
m("p", {style: get(styles, index)}, "p1"),
|
||||
m("p", {style: get(styles, index + 1)}, "p2"),
|
||||
m("p", {style: get(styles, index * 2)}, "p3"),
|
||||
m("p.zooga", {style: get(styles, index * 3 + 1), className: get(classes, index * 7)}, "p4")
|
||||
)
|
||||
)
|
||||
)
|
||||
return vnodes
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -311,16 +368,9 @@ var Root = {
|
|||
}
|
||||
}
|
||||
|
||||
suite.add({
|
||||
name : "repeated trees (recycling)",
|
||||
fn : function () {
|
||||
m.render(scratch, [m(Root)])
|
||||
m.render(scratch, [])
|
||||
}
|
||||
})
|
||||
|
||||
suite.add({
|
||||
name : "repeated trees (no recycling)",
|
||||
name : "repeated trees",
|
||||
fn : function () {
|
||||
m.render(scratch, [m(Root)])
|
||||
m.render(scratch, [])
|
||||
|
|
|
|||
112
promise/polyfill.js
Normal file
112
promise/polyfill.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"use strict"
|
||||
/** @constructor */
|
||||
var PromisePolyfill = function(executor) {
|
||||
if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`")
|
||||
if (typeof executor !== "function") throw new TypeError("executor must be a function")
|
||||
|
||||
var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
|
||||
var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
|
||||
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
function handler(list, shouldAbsorb) {
|
||||
return function execute(value) {
|
||||
var then
|
||||
try {
|
||||
if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
|
||||
if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
|
||||
executeOnce(then.bind(value))
|
||||
}
|
||||
else {
|
||||
callAsync(function() {
|
||||
if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
|
||||
for (var i = 0; i < list.length; i++) list[i](value)
|
||||
resolvers.length = 0, rejectors.length = 0
|
||||
instance.state = shouldAbsorb
|
||||
instance.retry = function() {execute(value)}
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
rejectCurrent(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
function executeOnce(then) {
|
||||
var runs = 0
|
||||
function run(fn) {
|
||||
return function(value) {
|
||||
if (runs++ > 0) return
|
||||
fn(value)
|
||||
}
|
||||
}
|
||||
var onerror = run(rejectCurrent)
|
||||
try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
|
||||
}
|
||||
|
||||
executeOnce(executor)
|
||||
}
|
||||
PromisePolyfill.prototype.then = function(onFulfilled, onRejection) {
|
||||
var self = this, instance = self._instance
|
||||
function handle(callback, list, next, state) {
|
||||
list.push(function(value) {
|
||||
if (typeof callback !== "function") next(value)
|
||||
else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
|
||||
})
|
||||
if (typeof instance.retry === "function" && state === instance.state) instance.retry()
|
||||
}
|
||||
var resolveNext, rejectNext
|
||||
var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
|
||||
handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
|
||||
return promise
|
||||
}
|
||||
PromisePolyfill.prototype.catch = function(onRejection) {
|
||||
return this.then(null, onRejection)
|
||||
}
|
||||
PromisePolyfill.prototype.finally = function(callback) {
|
||||
return this.then(
|
||||
function(value) {
|
||||
return PromisePolyfill.resolve(callback()).then(function() {
|
||||
return value
|
||||
})
|
||||
},
|
||||
function(reason) {
|
||||
return PromisePolyfill.resolve(callback()).then(function() {
|
||||
return PromisePolyfill.reject(reason);
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
PromisePolyfill.resolve = function(value) {
|
||||
if (value instanceof PromisePolyfill) return value
|
||||
return new PromisePolyfill(function(resolve) {resolve(value)})
|
||||
}
|
||||
PromisePolyfill.reject = function(value) {
|
||||
return new PromisePolyfill(function(resolve, reject) {reject(value)})
|
||||
}
|
||||
PromisePolyfill.all = function(list) {
|
||||
return new PromisePolyfill(function(resolve, reject) {
|
||||
var total = list.length, count = 0, values = []
|
||||
if (list.length === 0) resolve([])
|
||||
else for (var i = 0; i < list.length; i++) {
|
||||
(function(i) {
|
||||
function consume(value) {
|
||||
count++
|
||||
values[i] = value
|
||||
if (count === total) resolve(values)
|
||||
}
|
||||
if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
|
||||
list[i].then(consume, reject)
|
||||
}
|
||||
else consume(list[i])
|
||||
})(i)
|
||||
}
|
||||
})
|
||||
}
|
||||
PromisePolyfill.race = function(list) {
|
||||
return new PromisePolyfill(function(resolve, reject) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
list[i].then(resolve, reject)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = PromisePolyfill
|
||||
|
|
@ -1,105 +1,20 @@
|
|||
"use strict"
|
||||
/** @constructor */
|
||||
var PromisePolyfill = function(executor) {
|
||||
if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`")
|
||||
if (typeof executor !== "function") throw new TypeError("executor must be a function")
|
||||
|
||||
var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
|
||||
var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
|
||||
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
function handler(list, shouldAbsorb) {
|
||||
return function execute(value) {
|
||||
var then
|
||||
try {
|
||||
if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
|
||||
if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
|
||||
executeOnce(then.bind(value))
|
||||
}
|
||||
else {
|
||||
callAsync(function() {
|
||||
if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
|
||||
for (var i = 0; i < list.length; i++) list[i](value)
|
||||
resolvers.length = 0, rejectors.length = 0
|
||||
instance.state = shouldAbsorb
|
||||
instance.retry = function() {execute(value)}
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
rejectCurrent(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
function executeOnce(then) {
|
||||
var runs = 0
|
||||
function run(fn) {
|
||||
return function(value) {
|
||||
if (runs++ > 0) return
|
||||
fn(value)
|
||||
}
|
||||
}
|
||||
var onerror = run(rejectCurrent)
|
||||
try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
|
||||
}
|
||||
|
||||
executeOnce(executor)
|
||||
}
|
||||
PromisePolyfill.prototype.then = function(onFulfilled, onRejection) {
|
||||
var self = this, instance = self._instance
|
||||
function handle(callback, list, next, state) {
|
||||
list.push(function(value) {
|
||||
if (typeof callback !== "function") next(value)
|
||||
else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
|
||||
})
|
||||
if (typeof instance.retry === "function" && state === instance.state) instance.retry()
|
||||
}
|
||||
var resolveNext, rejectNext
|
||||
var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
|
||||
handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
|
||||
return promise
|
||||
}
|
||||
PromisePolyfill.prototype.catch = function(onRejection) {
|
||||
return this.then(null, onRejection)
|
||||
}
|
||||
PromisePolyfill.resolve = function(value) {
|
||||
if (value instanceof PromisePolyfill) return value
|
||||
return new PromisePolyfill(function(resolve) {resolve(value)})
|
||||
}
|
||||
PromisePolyfill.reject = function(value) {
|
||||
return new PromisePolyfill(function(resolve, reject) {reject(value)})
|
||||
}
|
||||
PromisePolyfill.all = function(list) {
|
||||
return new PromisePolyfill(function(resolve, reject) {
|
||||
var total = list.length, count = 0, values = []
|
||||
if (list.length === 0) resolve([])
|
||||
else for (var i = 0; i < list.length; i++) {
|
||||
(function(i) {
|
||||
function consume(value) {
|
||||
count++
|
||||
values[i] = value
|
||||
if (count === total) resolve(values)
|
||||
}
|
||||
if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
|
||||
list[i].then(consume, reject)
|
||||
}
|
||||
else consume(list[i])
|
||||
})(i)
|
||||
}
|
||||
})
|
||||
}
|
||||
PromisePolyfill.race = function(list) {
|
||||
return new PromisePolyfill(function(resolve, reject) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
list[i].then(resolve, reject)
|
||||
}
|
||||
})
|
||||
}
|
||||
var PromisePolyfill = require("./polyfill")
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
if (typeof window.Promise === "undefined") window.Promise = PromisePolyfill
|
||||
if (typeof window.Promise === "undefined") {
|
||||
window.Promise = PromisePolyfill
|
||||
} else if (!window.Promise.prototype.finally) {
|
||||
window.Promise.prototype.finally = PromisePolyfill.prototype.finally
|
||||
}
|
||||
module.exports = window.Promise
|
||||
} else if (typeof global !== "undefined") {
|
||||
if (typeof global.Promise === "undefined") global.Promise = PromisePolyfill
|
||||
if (typeof global.Promise === "undefined") {
|
||||
global.Promise = PromisePolyfill
|
||||
} else if (!global.Promise.prototype.finally) {
|
||||
global.Promise.prototype.finally = PromisePolyfill.prototype.finally
|
||||
}
|
||||
module.exports = global.Promise
|
||||
} else {
|
||||
module.exports = PromisePolyfill
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var Promise = require("../../promise/promise")
|
||||
var Promise = require("../../promise/polyfill")
|
||||
|
||||
o.spec("promise", function() {
|
||||
o.spec("constructor", function() {
|
||||
|
|
@ -15,6 +15,7 @@ o.spec("promise", function() {
|
|||
o("constructor has correct methods", function() {
|
||||
o(typeof Promise.prototype.then).equals("function")
|
||||
o(typeof Promise.prototype.catch).equals("function")
|
||||
o(typeof Promise.prototype.finally).equals("function")
|
||||
o(typeof Promise.resolve).equals("function")
|
||||
o(typeof Promise.reject).equals("function")
|
||||
o(typeof Promise.race).equals("function")
|
||||
|
|
@ -53,6 +54,78 @@ o.spec("promise", function() {
|
|||
o(value).equals(1)
|
||||
}).then(done)
|
||||
})
|
||||
o("finally lets a fulfilled value pass though", function(done) {
|
||||
var promise = Promise.resolve(1)
|
||||
var spy = o.spy(function(){return 2})
|
||||
|
||||
promise.finally(spy).then(function(value){
|
||||
o(value).equals(1)
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(0)
|
||||
o(spy.this).equals(undefined)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("finally lets a rejected reason pass though", function(done) {
|
||||
var promise = Promise.reject(1)
|
||||
var spy = o.spy(function(){return 2})
|
||||
|
||||
promise.finally(spy).catch(function(reason){
|
||||
o(reason).equals(1)
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(0)
|
||||
o(spy.this).equals(undefined)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("finally overrrides a fulfilled value when it throws", function(done) {
|
||||
var promise = Promise.resolve(1)
|
||||
var spy = o.spy(function(){throw 2})
|
||||
|
||||
promise.finally(spy).catch(function(reason){
|
||||
o(reason).equals(2)
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(0)
|
||||
o(spy.this).equals(undefined)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("finally overrrides a fulfilled value when it returns a rejected Promise", function(done) {
|
||||
var promise = Promise.resolve(1)
|
||||
var spy = o.spy(function(){return Promise.reject(2)})
|
||||
|
||||
promise.finally(spy).catch(function(reason){
|
||||
o(reason).equals(2)
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(0)
|
||||
o(spy.this).equals(undefined)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("finally overrrides a rejected reason when it throws", function(done) {
|
||||
var promise = Promise.reject(1)
|
||||
var spy = o.spy(function(){throw 2})
|
||||
|
||||
promise.finally(spy).catch(function(reason){
|
||||
o(reason).equals(2)
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(0)
|
||||
o(spy.this).equals(undefined)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("finally overrrides a rejected reason when it returns a rejected Promise", function(done) {
|
||||
var promise = Promise.reject(1)
|
||||
var spy = o.spy(function(){return Promise.reject(2)})
|
||||
|
||||
promise.finally(spy).catch(function(reason){
|
||||
o(reason).equals(2)
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(0)
|
||||
o(spy.this).equals(undefined)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("resolve", function() {
|
||||
o("resolves once", function(done) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ function compileSelector(selector) {
|
|||
|
||||
function execSelector(state, attrs, children) {
|
||||
var hasAttrs = false, childList, text
|
||||
var className = attrs.className || attrs.class
|
||||
var classAttr = hasOwn.call(attrs, "class") ? "class" : "className"
|
||||
var className = attrs[classAttr]
|
||||
|
||||
if (!isEmpty(state.attrs) && !isEmpty(attrs)) {
|
||||
var newAttrs = {}
|
||||
|
|
@ -46,21 +47,20 @@ function execSelector(state, attrs, children) {
|
|||
}
|
||||
|
||||
for (var key in state.attrs) {
|
||||
if (hasOwn.call(state.attrs, key)) {
|
||||
if (hasOwn.call(state.attrs, key) && key !== "className" && !hasOwn.call(attrs, key)){
|
||||
attrs[key] = state.attrs[key]
|
||||
}
|
||||
}
|
||||
if (className != null || state.attrs.className != null) attrs.className =
|
||||
className != null
|
||||
? state.attrs.className != null
|
||||
? state.attrs.className + " " + className
|
||||
: className
|
||||
: state.attrs.className != null
|
||||
? state.attrs.className
|
||||
: null
|
||||
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
|
||||
if (state.attrs.className != null) {
|
||||
attrs.className = state.attrs.className + " " + className
|
||||
}
|
||||
}
|
||||
if (classAttr === "class") attrs.class = null
|
||||
|
||||
for (var key in attrs) {
|
||||
if (hasOwn.call(attrs, key) && key !== "key") {
|
||||
|
|
@ -75,20 +75,15 @@ function execSelector(state, attrs, children) {
|
|||
childList = children
|
||||
}
|
||||
|
||||
return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text)
|
||||
return Vnode(state.tag, attrs.key, hasAttrs ? attrs : null, childList, text)
|
||||
}
|
||||
|
||||
function hyperscript(selector) {
|
||||
// Because sloppy mode sucks
|
||||
var attrs = arguments[1], start = 2, children
|
||||
|
||||
if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
|
||||
throw Error("The selector must be either a string or a component.");
|
||||
}
|
||||
|
||||
if (typeof selector === "string") {
|
||||
var cached = selectorCache[selector] || compileSelector(selector)
|
||||
}
|
||||
var attrs = arguments[1], start = 2, children
|
||||
|
||||
if (attrs == null) {
|
||||
attrs = {}
|
||||
|
|
@ -105,12 +100,10 @@ function hyperscript(selector) {
|
|||
while (start < arguments.length) children.push(arguments[start++])
|
||||
}
|
||||
|
||||
var normalized = Vnode.normalizeChildren(children)
|
||||
|
||||
if (typeof selector === "string") {
|
||||
return execSelector(cached, attrs, normalized)
|
||||
return execSelector(selectorCache[selector] || compileSelector(selector), attrs, Vnode.normalizeChildren(children))
|
||||
} else {
|
||||
return Vnode(selector, attrs.key, attrs, normalized)
|
||||
return Vnode(selector, attrs.key, attrs, children)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
742
render/render.js
742
render/render.js
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,7 @@
|
|||
<script src="test-fragment.js"></script>
|
||||
<script src="test-normalize.js"></script>
|
||||
<script src="test-normalizeChildren.js"></script>
|
||||
<script src="test-normalizeComponentChildren.js"></script>
|
||||
<script src="test-createText.js"></script>
|
||||
<script src="test-createHTML.js"></script>
|
||||
<script src="test-createFragment.js"></script>
|
||||
|
|
|
|||
|
|
@ -44,47 +44,92 @@ o.spec("attributes", function() {
|
|||
o(b.dom.hasAttribute("id")).equals(true)
|
||||
o(b.dom.getAttribute("id")).equals("test")
|
||||
|
||||
// #1804
|
||||
render(root, [c]);
|
||||
|
||||
// #1804
|
||||
// TODO: uncomment
|
||||
// o(c.dom.hasAttribute("id")).equals(false)
|
||||
o(c.dom.hasAttribute("id")).equals(false)
|
||||
})
|
||||
})
|
||||
o.spec("customElements", function(){
|
||||
|
||||
o("when vnode is customElement, custom setAttribute called", function(){
|
||||
|
||||
var normal = [
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}}
|
||||
]
|
||||
|
||||
var custom = [
|
||||
{tag: "custom-element", attrs: {custom: "x"}},
|
||||
{tag: "input", attrs: {is: "something-special", custom: "x"}},
|
||||
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
|
||||
]
|
||||
|
||||
var view = normal.concat(custom)
|
||||
|
||||
o("when vnode is customElement without property, custom setAttribute called", function(){
|
||||
var f = $window.document.createElement
|
||||
var spy
|
||||
var spies = []
|
||||
|
||||
$window.document.createElement = function(tag, is){
|
||||
var el = f(tag, is)
|
||||
if(!spy){
|
||||
spy = o.spy(el.setAttribute)
|
||||
}
|
||||
var spy = o.spy(el.setAttribute)
|
||||
el.setAttribute = spy
|
||||
|
||||
spies.push(spy)
|
||||
spy.elem = el
|
||||
return el
|
||||
}
|
||||
|
||||
render(root, view)
|
||||
render(root, [
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "custom-element", attrs: {custom: "x"}},
|
||||
{tag: "input", attrs: {is: "something-special", custom: "x"}},
|
||||
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
|
||||
])
|
||||
|
||||
o(spy.callCount).equals(custom.length)
|
||||
o(spies[0].callCount).equals(0)
|
||||
o(spies[1].callCount).equals(0)
|
||||
o(spies[2].callCount).equals(0)
|
||||
o(spies[3].calls).deepEquals([{this: spies[3].elem, args: ["custom", "x"]}])
|
||||
o(spies[4].calls).deepEquals([{this: spies[4].elem, args: ["custom", "x"]}])
|
||||
o(spies[5].calls).deepEquals([{this: spies[5].elem, args: ["custom", "x"]}])
|
||||
})
|
||||
|
||||
o("when vnode is customElement with property, custom setAttribute not called", function(){
|
||||
var f = $window.document.createElement
|
||||
var spies = []
|
||||
var getters = []
|
||||
var setters = []
|
||||
|
||||
$window.document.createElement = function(tag, is){
|
||||
var el = f(tag, is)
|
||||
var spy = o.spy(el.setAttribute)
|
||||
el.setAttribute = spy
|
||||
spies.push(spy)
|
||||
spy.elem = el
|
||||
if (tag === "custom-element" || is && is.is === "something-special") {
|
||||
var custom = "foo"
|
||||
var getter, setter
|
||||
Object.defineProperty(el, "custom", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: getter = o.spy(function () { return custom }),
|
||||
set: setter = o.spy(function (value) { custom = value })
|
||||
})
|
||||
getters.push(getter)
|
||||
setters.push(setter)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
render(root, [
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "custom-element", attrs: {custom: "x"}},
|
||||
{tag: "input", attrs: {is: "something-special", custom: "x"}},
|
||||
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
|
||||
])
|
||||
|
||||
o(spies[0].callCount).equals(0)
|
||||
o(spies[1].callCount).equals(0)
|
||||
o(spies[2].callCount).equals(0)
|
||||
o(spies[3].callCount).equals(0)
|
||||
o(spies[4].callCount).equals(0)
|
||||
o(spies[5].callCount).equals(0)
|
||||
o(getters[0].callCount).equals(0)
|
||||
o(getters[1].callCount).equals(0)
|
||||
o(getters[2].callCount).equals(0)
|
||||
o(setters[0].calls).deepEquals([{this: spies[3].elem, args: ["x"]}])
|
||||
o(setters[1].calls).deepEquals([{this: spies[4].elem, args: ["x"]}])
|
||||
o(setters[2].calls).deepEquals([{this: spies[5].elem, args: ["x"]}])
|
||||
})
|
||||
|
||||
})
|
||||
|
|
@ -147,7 +192,7 @@ o.spec("attributes", function() {
|
|||
o("a lack of attribute removes `value`", function() {
|
||||
var a = {tag: "input", attrs: {}}
|
||||
var b = {tag: "input", attrs: {value: "test"}}
|
||||
// var c = {tag: "input", attrs: {}}
|
||||
var c = {tag: "input", attrs: {}}
|
||||
|
||||
render(root, [a])
|
||||
|
||||
|
|
@ -158,10 +203,9 @@ o.spec("attributes", function() {
|
|||
o(a.dom.value).equals("test")
|
||||
|
||||
// https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235
|
||||
// TODO: Uncomment
|
||||
// render(root, [c])
|
||||
render(root, [c])
|
||||
|
||||
// o(a.dom.value).equals("")
|
||||
o(a.dom.value).equals("")
|
||||
})
|
||||
o("can be set as number", function() {
|
||||
var a = {tag: "input", attrs: {value: 1}}
|
||||
|
|
@ -276,17 +320,16 @@ o.spec("attributes", function() {
|
|||
o.spec("textarea.value", function() {
|
||||
o("can be removed by not passing a value", function() {
|
||||
var a = {tag: "textarea", attrs: {value:"x"}}
|
||||
// var b = {tag: "textarea", attrs: {}}
|
||||
var b = {tag: "textarea", attrs: {}}
|
||||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.value).equals("x")
|
||||
|
||||
// https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235
|
||||
// TODO: Uncomment
|
||||
// render(root, [b])
|
||||
render(root, [b])
|
||||
|
||||
// o(b.dom.value).equals("")
|
||||
o(b.dom.value).equals("")
|
||||
})
|
||||
o("isn't set when equivalent to the previous value and focused", function() {
|
||||
var $window = domMock({spy: o.spy})
|
||||
|
|
@ -352,7 +395,7 @@ o.spec("attributes", function() {
|
|||
o(canvas.dom.width).equals(100)
|
||||
})
|
||||
})
|
||||
o.spec("svg class", function() {
|
||||
o.spec("svg", function() {
|
||||
o("when className is specified then it should be added as a class", function() {
|
||||
var a = {tag: "svg", attrs: {className: "test"}}
|
||||
|
||||
|
|
@ -360,6 +403,26 @@ o.spec("attributes", function() {
|
|||
|
||||
o(a.dom.attributes["class"].value).equals("test")
|
||||
})
|
||||
/* eslint-disable no-script-url */
|
||||
o("handles xlink:href", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [
|
||||
{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}}
|
||||
]}
|
||||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.nodeName).equals("svg")
|
||||
o(vnode.dom.firstChild.attributes["href"].value).equals("javascript:;")
|
||||
o(vnode.dom.firstChild.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
|
||||
vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [
|
||||
{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {}}
|
||||
]}
|
||||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.nodeName).equals("svg")
|
||||
o("href" in vnode.dom.firstChild.attributes).equals(false)
|
||||
})
|
||||
/* eslint-enable no-script-url */
|
||||
})
|
||||
o.spec("option.value", function() {
|
||||
o("can be set as text", function() {
|
||||
|
|
@ -376,7 +439,7 @@ o.spec("attributes", function() {
|
|||
|
||||
o(a.dom.value).equals("1")
|
||||
})
|
||||
o("null becomes the empty string", function() {
|
||||
o("null removes the attribute", function() {
|
||||
var a = {tag: "option", attrs: {value: null}}
|
||||
var b = {tag: "option", attrs: {value: "test"}}
|
||||
var c = {tag: "option", attrs: {value: null}}
|
||||
|
|
@ -384,7 +447,7 @@ o.spec("attributes", function() {
|
|||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
o(a.dom.getAttribute("value")).equals("")
|
||||
o(a.dom.hasAttribute("value")).equals(false)
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
|
|
@ -394,7 +457,7 @@ o.spec("attributes", function() {
|
|||
render(root, [c]);
|
||||
|
||||
o(c.dom.value).equals("")
|
||||
o(c.dom.getAttribute("value")).equals("")
|
||||
o(c.dom.hasAttribute("value")).equals(false)
|
||||
})
|
||||
o("'' and 0 are different values", function() {
|
||||
var a = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]}
|
||||
|
|
@ -462,6 +525,19 @@ o.spec("attributes", function() {
|
|||
{tag:"option", attrs: {value: ""}}
|
||||
]}
|
||||
}
|
||||
/* FIXME
|
||||
This incomplete test is meant for testing #1916.
|
||||
However it cannot be completed until #1978 is addressed
|
||||
which is a lack a working select.selected / option.selected
|
||||
attribute. Ask isiahmeadows.
|
||||
|
||||
o("render select options", function() {
|
||||
var select = {tag: "select", selectedIndex: 0, children: [
|
||||
{tag:"option", attrs: {value: "1", selected: ""}}
|
||||
]}
|
||||
render(root, select)
|
||||
})
|
||||
*/
|
||||
o("can be set as text", function() {
|
||||
var a = makeSelect()
|
||||
var b = makeSelect("2")
|
||||
|
|
|
|||
|
|
@ -764,97 +764,6 @@ o.spec("component", function() {
|
|||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
})
|
||||
o("lifecycle timing megatest (for a single component with the state overwritten)", function() {
|
||||
var methods = {
|
||||
view: o.spy(function(vnode) {
|
||||
o(vnode.state).equals(1)
|
||||
return ""
|
||||
})
|
||||
}
|
||||
var attrs = {}
|
||||
var hooks = [
|
||||
"oninit", "oncreate", "onbeforeupdate",
|
||||
"onupdate", "onbeforeremove", "onremove"
|
||||
]
|
||||
hooks.forEach(function(hook) {
|
||||
// the `attrs` hooks are called before the component ones
|
||||
attrs[hook] = o.spy(function(vnode) {
|
||||
o(vnode.state).equals(1)
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount + 1)
|
||||
})
|
||||
methods[hook] = o.spy(function(vnode) {
|
||||
o(vnode.state).equals(1)
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)
|
||||
})
|
||||
})
|
||||
|
||||
var attrsOninit = attrs.oninit
|
||||
var methodsOninit = methods.oninit
|
||||
attrs.oninit = o.spy(function(vnode){
|
||||
vnode.state = 1
|
||||
return attrsOninit.call(this, vnode)
|
||||
})
|
||||
methods.oninit = o.spy(function(vnode){
|
||||
vnode.state = 1
|
||||
return methodsOninit.call(this, vnode)
|
||||
})
|
||||
|
||||
var component = createComponent(methods)
|
||||
|
||||
o(methods.view.callCount).equals(0)
|
||||
o(methods.oninit.callCount).equals(0)
|
||||
o(methods.oncreate.callCount).equals(0)
|
||||
o(methods.onbeforeupdate.callCount).equals(0)
|
||||
o(methods.onupdate.callCount).equals(0)
|
||||
o(methods.onbeforeremove.callCount).equals(0)
|
||||
o(methods.onremove.callCount).equals(0)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
|
||||
render(root, [{tag: component, attrs: attrs}])
|
||||
|
||||
o(methods.view.callCount).equals(1)
|
||||
o(methods.oninit.callCount).equals(1)
|
||||
o(methods.oncreate.callCount).equals(1)
|
||||
o(methods.onbeforeupdate.callCount).equals(0)
|
||||
o(methods.onupdate.callCount).equals(0)
|
||||
o(methods.onbeforeremove.callCount).equals(0)
|
||||
o(methods.onremove.callCount).equals(0)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
|
||||
render(root, [{tag: component, attrs: attrs}])
|
||||
|
||||
o(methods.view.callCount).equals(2)
|
||||
o(methods.oninit.callCount).equals(1)
|
||||
o(methods.oncreate.callCount).equals(1)
|
||||
o(methods.onbeforeupdate.callCount).equals(1)
|
||||
o(methods.onupdate.callCount).equals(1)
|
||||
o(methods.onbeforeremove.callCount).equals(0)
|
||||
o(methods.onremove.callCount).equals(0)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
|
||||
render(root, [])
|
||||
|
||||
o(methods.view.callCount).equals(2)
|
||||
o(methods.oninit.callCount).equals(1)
|
||||
o(methods.oncreate.callCount).equals(1)
|
||||
o(methods.onbeforeupdate.callCount).equals(1)
|
||||
o(methods.onupdate.callCount).equals(1)
|
||||
o(methods.onbeforeremove.callCount).equals(1)
|
||||
o(methods.onremove.callCount).equals(1)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
})
|
||||
o("hook state and arguments validation", function(){
|
||||
var methods = {
|
||||
view: o.spy(function(vnode) {
|
||||
|
|
@ -899,7 +808,7 @@ o.spec("component", function() {
|
|||
o(methods[hook].args.length).equals(attrs[hook].args.length)(hook)
|
||||
})
|
||||
})
|
||||
o("recycled components get a fresh state", function() {
|
||||
o("no recycling occurs (was: recycled components get a fresh state)", function() {
|
||||
var step = 0
|
||||
var firstState
|
||||
var view = o.spy(function(vnode) {
|
||||
|
|
@ -918,7 +827,7 @@ o.spec("component", function() {
|
|||
step = 1
|
||||
render(root, [{tag: "div", children: [{tag: component, key: 1}]}])
|
||||
|
||||
o(child).equals(root.firstChild.firstChild)
|
||||
o(child).notEquals(root.firstChild.firstChild) // this used to be a recycling pool test
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-script-url */
|
||||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
|
|
@ -54,6 +53,7 @@ o.spec("createElement", function() {
|
|||
o(vnode.dom.childNodes[0].nodeName).equals("A")
|
||||
o(vnode.dom.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
/* eslint-disable no-script-url */
|
||||
o("creates svg", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [
|
||||
{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}},
|
||||
|
|
@ -71,6 +71,7 @@ o.spec("createElement", function() {
|
|||
o(vnode.dom.childNodes[1].firstChild.nodeName).equals("body")
|
||||
o(vnode.dom.childNodes[1].firstChild.namespaceURI).equals("http://www.w3.org/1999/xhtml")
|
||||
})
|
||||
/* eslint-enable no-script-url */
|
||||
o("sets attributes correctly for svg", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", attrs: {viewBox: "0 0 100 100"}}
|
||||
render(root, [vnode])
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ o.spec("createHTML", function() {
|
|||
o(vnode.dom).equals(null)
|
||||
o(vnode.domSize).equals(0)
|
||||
})
|
||||
o("handles multiple children", function() {
|
||||
o("handles multiple children in HTML", function() {
|
||||
var vnode = {tag: "<", children: "<a></a><b></b>"}
|
||||
render(root, [vnode])
|
||||
|
||||
|
|
@ -51,4 +51,34 @@ o.spec("createHTML", function() {
|
|||
o(vnode.dom.nodeName).equals(tag.toUpperCase())
|
||||
})
|
||||
})
|
||||
o("creates SVG", function() {
|
||||
var vnode = {tag: "<", children: "<g></g>"}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.dom.nodeName).equals("g")
|
||||
o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
o("creates text SVG", function() {
|
||||
var vnode = {tag: "<", children: "a"}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.dom.nodeValue).equals("a")
|
||||
})
|
||||
o("handles empty SVG", function() {
|
||||
var vnode = {tag: "<", children: ""}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.dom).equals(null)
|
||||
o(vnode.domSize).equals(0)
|
||||
})
|
||||
o("handles multiple children in SVG", function() {
|
||||
var vnode = {tag: "<", children: "<g></g><text></text>"}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.domSize).equals(2)
|
||||
o(vnode.dom.nodeName).equals("g")
|
||||
o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(vnode.dom.nextSibling.nodeName).equals("text")
|
||||
o(vnode.dom.nextSibling.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,8 +32,75 @@ o.spec("event", function() {
|
|||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(false)
|
||||
o(e.$propagationStopped).equals(false)
|
||||
})
|
||||
|
||||
|
||||
o("handles onclick returning false", function() {
|
||||
var spy = o.spy(function () { return false })
|
||||
var div = {tag: "div", attrs: {onclick: spy}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(div.dom)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(true)
|
||||
o(e.$propagationStopped).equals(true)
|
||||
})
|
||||
|
||||
o("handles click EventListener object", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {onclick: listener}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(false)
|
||||
o(e.$propagationStopped).equals(false)
|
||||
})
|
||||
|
||||
o("handles click EventListener object returning false", function() {
|
||||
var spy = o.spy(function () { return false })
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {onclick: listener}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(false)
|
||||
o(e.$propagationStopped).equals(false)
|
||||
})
|
||||
|
||||
o("removes event", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {onclick: spy}}
|
||||
|
|
@ -45,7 +112,130 @@ o.spec("event", function() {
|
|||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event when null", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {onclick: spy}}
|
||||
var updated = {tag: "a", attrs: {onclick: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event when undefined", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {onclick: spy}}
|
||||
var updated = {tag: "a", attrs: {onclick: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event added via addEventListener when null", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {ontouchstart: spy}}
|
||||
var updated = {tag: "a", attrs: {ontouchstart: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("TouchEvents")
|
||||
e.initEvent("touchstart", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event added via addEventListener", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {ontouchstart: spy}}
|
||||
var updated = {tag: "a", attrs: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("TouchEvents")
|
||||
e.initEvent("touchstart", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event added via addEventListener when undefined", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {ontouchstart: spy}}
|
||||
var updated = {tag: "a", attrs: {ontouchstart: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("TouchEvents")
|
||||
e.initEvent("touchstart", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes EventListener object", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var vnode = {tag: "a", attrs: {onclick: listener}}
|
||||
var updated = {tag: "a", attrs: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes EventListener object when null", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var vnode = {tag: "a", attrs: {onclick: listener}}
|
||||
var updated = {tag: "a", attrs: {onclick: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes EventListener object when undefined", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var vnode = {tag: "a", attrs: {onclick: listener}}
|
||||
var updated = {tag: "a", attrs: {onclick: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
|
|
@ -72,6 +262,30 @@ o.spec("event", function() {
|
|||
o(div.dom.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("fires click EventListener object only once after redraw", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {id: "a", onclick: listener}}
|
||||
var updated = {tag: "div", attrs: {id: "b", onclick: listener}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
render(root, [updated])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(div.dom).equals(updated.dom)
|
||||
o(div.dom.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("handles ontransitionend", function() {
|
||||
var spy = o.spy()
|
||||
var div = {tag: "div", attrs: {ontransitionend: spy}}
|
||||
|
|
@ -90,4 +304,24 @@ o.spec("event", function() {
|
|||
o(onevent.args[0].type).equals("transitionend")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
})
|
||||
|
||||
o("handles transitionend EventListener object", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {ontransitionend: listener}}
|
||||
var e = $window.document.createEvent("HTMLEvents")
|
||||
e.initEvent("transitionend", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("transitionend")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("transitionend")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -16,52 +16,51 @@ o.spec("hyperscript", function() {
|
|||
|
||||
o(vnode.tag).equals("a")
|
||||
})
|
||||
o("v1.0.1 bug-for-bug regression suite", function(){
|
||||
o("class and className normalization", function(){
|
||||
o(m("a", {
|
||||
class: null
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: null
|
||||
class: null
|
||||
})
|
||||
o(m("a", {
|
||||
class: undefined
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null
|
||||
})
|
||||
o(m("a", {
|
||||
class: false
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: false
|
||||
})
|
||||
o(m("a", {
|
||||
class: true
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: true
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: null
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: "x null"
|
||||
class: null,
|
||||
className: "x"
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: undefined
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: "x"
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: false
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: "x false"
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: true
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: "x true"
|
||||
})
|
||||
o(m("a", {
|
||||
|
|
@ -97,7 +96,7 @@ o.spec("hyperscript", function() {
|
|||
o(m("a.x", {
|
||||
className: false
|
||||
}).attrs).deepEquals({
|
||||
className: "x"
|
||||
className: "x false"
|
||||
})
|
||||
o(m("a.x", {
|
||||
className: true
|
||||
|
|
@ -272,7 +271,7 @@ o.spec("hyperscript", function() {
|
|||
var vnode = m("div", {key:"a"})
|
||||
|
||||
o(vnode.tag).equals("div")
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.key).equals("a")
|
||||
})
|
||||
o("handles many attrs", function() {
|
||||
|
|
@ -303,6 +302,63 @@ o.spec("hyperscript", function() {
|
|||
o(vnode.attrs.className).equals("a b")
|
||||
})
|
||||
})
|
||||
o.spec("custom element attrs", function() {
|
||||
o("handles string attr", function() {
|
||||
var vnode = m("custom-element", {a: "b"})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals("b")
|
||||
})
|
||||
o("handles falsy string attr", function() {
|
||||
var vnode = m("custom-element", {a: ""})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals("")
|
||||
})
|
||||
o("handles number attr", function() {
|
||||
var vnode = m("custom-element", {a: 1})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(1)
|
||||
})
|
||||
o("handles falsy number attr", function() {
|
||||
var vnode = m("custom-element", {a: 0})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(0)
|
||||
})
|
||||
o("handles boolean attr", function() {
|
||||
var vnode = m("custom-element", {a: true})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(true)
|
||||
})
|
||||
o("handles falsy boolean attr", function() {
|
||||
var vnode = m("custom-element", {a: false})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(false)
|
||||
})
|
||||
o("handles only key in attrs", function() {
|
||||
var vnode = m("custom-element", {key:"a"})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.key).equals("a")
|
||||
})
|
||||
o("handles many attrs", function() {
|
||||
var vnode = m("custom-element", {a: "b", c: "d"})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals("b")
|
||||
o(vnode.attrs.c).equals("d")
|
||||
})
|
||||
o("handles className attrs property", function() {
|
||||
var vnode = m("custom-element", {className: "a"})
|
||||
|
||||
o(vnode.attrs.className).equals("a")
|
||||
})
|
||||
})
|
||||
o.spec("children", function() {
|
||||
o("handles string single child", function() {
|
||||
var vnode = m("div", {}, ["a"])
|
||||
|
|
@ -490,20 +546,20 @@ o.spec("hyperscript", function() {
|
|||
o("handles children without attr", function() {
|
||||
var vnode = m("div", [m("i"), m("s")])
|
||||
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.children[0].tag).equals("i")
|
||||
o(vnode.children[1].tag).equals("s")
|
||||
})
|
||||
o("handles child without attr unwrapped", function() {
|
||||
var vnode = m("div", m("i"))
|
||||
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.children[0].tag).equals("i")
|
||||
})
|
||||
o("handles children without attr unwrapped", function() {
|
||||
var vnode = m("div", m("i"), m("s"))
|
||||
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.children[0].tag).equals("i")
|
||||
o(vnode.children[1].tag).equals("s")
|
||||
})
|
||||
|
|
@ -524,6 +580,15 @@ o.spec("hyperscript", function() {
|
|||
m(".a", attrs)
|
||||
o(attrs).deepEquals({a: "b"})
|
||||
})
|
||||
o("non-nullish attr takes precedence over selector", function() {
|
||||
o(m("[a=b]", {a: "c"}).attrs).deepEquals({a: "c"})
|
||||
})
|
||||
o("null attr takes precedence over selector", function() {
|
||||
o(m("[a=b]", {a: null}).attrs).deepEquals({a: null})
|
||||
})
|
||||
o("undefined attr takes precedence over selector", function() {
|
||||
o(m("[a=b]", {a: undefined}).attrs).deepEquals({a: undefined})
|
||||
})
|
||||
o("handles fragment children without attr unwrapped", function() {
|
||||
var vnode = m("div", [m("i")], [m("s")])
|
||||
|
||||
|
|
@ -551,19 +616,29 @@ o.spec("hyperscript", function() {
|
|||
o.spec("components", function() {
|
||||
o("works with POJOs", function() {
|
||||
var component = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
view: function() {}
|
||||
}
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
||||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
o(vnode.children[0].tag).equals("#")
|
||||
o(vnode.children[0].children).equals("b")
|
||||
o(vnode.children[0]).equals("b")
|
||||
})
|
||||
o("works with functions", function() {
|
||||
o("works with constructibles", function() {
|
||||
var component = o.spy()
|
||||
component.prototype.view = function() {}
|
||||
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
||||
o(component.callCount).equals(0)
|
||||
|
||||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
o(vnode.children[0]).equals("b")
|
||||
})
|
||||
o("works with closures", function () {
|
||||
var component = o.spy()
|
||||
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
|
@ -573,8 +648,7 @@ o.spec("hyperscript", function() {
|
|||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
o(vnode.children[0].tag).equals("#")
|
||||
o(vnode.children[0].children).equals("b")
|
||||
o(vnode.children[0]).equals("b")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -59,6 +59,16 @@ o.spec("form inputs", function() {
|
|||
o(updated.dom.value).equals("aaa")
|
||||
})
|
||||
|
||||
o("clear element value if vdom value is set to undefined (aka removed)", function() {
|
||||
var input = {tag: "input", attrs: {value: "aaa", oninput: function() {}}}
|
||||
var updated = {tag: "input", attrs: {value: undefined, oninput: function() {}}}
|
||||
|
||||
render(root, [input])
|
||||
render(root, [updated])
|
||||
|
||||
o(updated.dom.value).equals("")
|
||||
})
|
||||
|
||||
o("syncs input checked attribute if DOM value differs from vdom value", function() {
|
||||
var input = {tag: "input", attrs: {type: "checkbox", checked: true, onclick: function() {}}}
|
||||
var updated = {tag: "input", attrs: {type: "checkbox", checked: true, onclick: function() {}}}
|
||||
|
|
|
|||
34
render/tests/test-normalizeComponentChildren.js
Normal file
34
render/tests/test-normalizeComponentChildren.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var m = require("../../render/hyperscript")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
o.spec("component children", function () {
|
||||
var $window = domMock()
|
||||
var root = $window.document.createElement("div")
|
||||
var render = vdom($window).render
|
||||
|
||||
o.spec("component children", function () {
|
||||
var component = {
|
||||
view: function (vnode) {
|
||||
return vnode.children
|
||||
}
|
||||
}
|
||||
|
||||
var vnode = m(component, "a")
|
||||
|
||||
render(root, vnode)
|
||||
|
||||
o("are not normalized on ingestion", function () {
|
||||
o(vnode.children[0]).equals("a")
|
||||
})
|
||||
|
||||
o("are normalized upon view interpolation", function () {
|
||||
o(vnode.instance.children.length).equals(1)
|
||||
o(vnode.instance.children[0].tag).equals("#")
|
||||
o(vnode.instance.children[0].children).equals("a")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -36,10 +36,7 @@ o.spec("onbeforeremove", function() {
|
|||
o(update.callCount).equals(0)
|
||||
})
|
||||
o("calls onbeforeremove when removing element", function(done) {
|
||||
var vnode = {tag: "div", attrs: {
|
||||
oninit: function() {vnode.state = {}},
|
||||
onbeforeremove: remove
|
||||
}}
|
||||
var vnode = {tag: "div", attrs: {onbeforeremove: remove}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [])
|
||||
|
|
@ -47,6 +44,7 @@ o.spec("onbeforeremove", function() {
|
|||
function remove(node) {
|
||||
o(node).equals(vnode)
|
||||
o(this).equals(vnode.state)
|
||||
o(this != null && typeof this === "object").equals(true)
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(root.firstChild).equals(vnode.dom)
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom).equals(updated[0].dom)
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(updated[0].dom.nodeName).equals("DIV")
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -199,4 +199,27 @@ o.spec("oninit", function() {
|
|||
o(vnode.dom.oninit).equals(undefined)
|
||||
o(vnode.dom.attributes["oninit"]).equals(undefined)
|
||||
})
|
||||
|
||||
o("No spurious oninit calls in mapped keyed diff when the pool is involved (#1992)", function () {
|
||||
var oninit1 = o.spy()
|
||||
var oninit2 = o.spy()
|
||||
var oninit3 = o.spy()
|
||||
|
||||
render(root, [
|
||||
{tag: "p", key: 1, attrs: {oninit: oninit1}},
|
||||
{tag: "p", key: 2, attrs: {oninit: oninit2}},
|
||||
{tag: "p", key: 3, attrs: {oninit: oninit3}},
|
||||
])
|
||||
render(root, [
|
||||
{tag: "p", key: 1, attrs: {oninit: oninit1}},
|
||||
{tag: "p", key: 3, attrs: {oninit: oninit3}},
|
||||
])
|
||||
render(root, [
|
||||
{tag: "p", key: 3, attrs: {oninit: oninit3}},
|
||||
])
|
||||
|
||||
o(oninit1.callCount).equals(1)
|
||||
o(oninit2.callCount).equals(1)
|
||||
o(oninit3.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ o.spec("onremove", function() {
|
|||
o(vnode.dom.attributes["onremove"]).equals(undefined)
|
||||
o(vnode.events).equals(undefined)
|
||||
})
|
||||
o("calls onremove on recycle", function() {
|
||||
o("calls onremove on keyed nodes", function() {
|
||||
var remove = o.spy()
|
||||
var vnodes = [{tag: "div", key: 1}]
|
||||
var temp = [{tag: "div", key: 2, attrs: {onremove: remove}}]
|
||||
|
|
@ -101,6 +101,7 @@ o.spec("onremove", function() {
|
|||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(remove.callCount).equals(1)
|
||||
})
|
||||
o("does not recycle when there's an onremove", function() {
|
||||
|
|
@ -132,7 +133,7 @@ o.spec("onremove", function() {
|
|||
})
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("calls onremove on nested component child", function() {
|
||||
|
|
@ -148,7 +149,7 @@ o.spec("onremove", function() {
|
|||
})
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("doesn't call onremove on children when the corresponding view returns null (after removing the parent)", function() {
|
||||
|
|
@ -191,6 +192,28 @@ o.spec("onremove", function() {
|
|||
o(spy.callCount).equals(0)
|
||||
o(threw).equals(false)
|
||||
})
|
||||
o("onremove doesn't fire on nodes that go from pool to pool (#1990)", function() {
|
||||
var onremove = o.spy();
|
||||
|
||||
render(root, [m("div", m("div")), m("div", m("div", {onremove: onremove}))]);
|
||||
render(root, [m("div", m("div"))]);
|
||||
render(root, []);
|
||||
|
||||
o(onremove.callCount).equals(1)
|
||||
})
|
||||
o("doesn't fire when removing the children of a node that's brought back from the pool (#1991 part 2)", function() {
|
||||
var onremove = o.spy()
|
||||
var vnode = {tag: "div", key: 1, children: [{tag: "div", attrs: {onremove: onremove}}]}
|
||||
var temp = {tag: "div", key: 2}
|
||||
var updated = {tag: "div", key: 1, children: [{tag: "p"}]}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [temp])
|
||||
render(root, [updated])
|
||||
|
||||
o(vnode.dom).notEquals(updated.dom) // this used to be a recycling pool test
|
||||
o(onremove.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
614
render/tests/test-render-hyperscript-integration.js
Normal file
614
render/tests/test-render-hyperscript-integration.js
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var m = require("../../render/hyperscript")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
o.spec("render/hyperscript integration", function() {
|
||||
var $window, root, render
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
root = $window.document.createElement("div")
|
||||
render = vdom($window).render
|
||||
})
|
||||
o.spec("setting class", function() {
|
||||
o("selector only", function() {
|
||||
render(root, m(".foo"))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("class only", function() {
|
||||
render(root, m("div", {class: "foo"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("className only", function() {
|
||||
render(root, m("div", {className: "foo"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and class", function() {
|
||||
render(root, m(".bar", {class: "foo"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar", "foo"])
|
||||
})
|
||||
o("selector and className", function() {
|
||||
render(root, m(".bar", {className: "foo"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar", "foo"])
|
||||
})
|
||||
o("selector and a null class", function() {
|
||||
render(root, m(".foo", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and a null className", function() {
|
||||
render(root, m(".foo", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and an undefined class", function() {
|
||||
render(root, m(".foo", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and an undefined className", function() {
|
||||
render(root, m(".foo", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
})
|
||||
o.spec("updating class", function() {
|
||||
o.spec("from selector only", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from class only", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from ", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from className only", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and class", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and className", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from and a null class", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and a null className", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and an undefined class", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and an undefined className", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -192,6 +192,85 @@ o.spec("updateElement", function() {
|
|||
o(updated.dom.style.backgroundColor).equals("")
|
||||
o(updated.dom.style.color).equals("gold")
|
||||
})
|
||||
o("does not re-render element styles for equivalent style objects", function() {
|
||||
var style = {color: "gold"}
|
||||
var vnode = {tag: "a", attrs: {style: style}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
root.firstChild.style.color = "red"
|
||||
style = {color: "gold"}
|
||||
var updated = {tag: "a", attrs: {style: style}}
|
||||
render(root, [updated])
|
||||
|
||||
o(updated.dom.style.color).equals("red")
|
||||
})
|
||||
o("setting style to `null` removes all styles", function() {
|
||||
var vnode = {"tag": "p", attrs: {style: "background-color: red"}}
|
||||
var updated = {"tag": "p", attrs: {style: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
o("style" in vnode.dom.attributes).equals(true)
|
||||
o(vnode.dom.attributes.style.value).equals("background-color: red;")
|
||||
|
||||
render(root, [updated])
|
||||
|
||||
//browsers disagree here
|
||||
try {
|
||||
|
||||
o(updated.dom.attributes.style.value).equals("")
|
||||
|
||||
} catch (e) {
|
||||
|
||||
o("style" in updated.dom.attributes).equals(false)
|
||||
|
||||
}
|
||||
})
|
||||
o("setting style to `undefined` removes all styles", function() {
|
||||
var vnode = {"tag": "p", attrs: {style: "background-color: red"}}
|
||||
var updated = {"tag": "p", attrs: {style: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
o("style" in vnode.dom.attributes).equals(true)
|
||||
o(vnode.dom.attributes.style.value).equals("background-color: red;")
|
||||
|
||||
render(root, [updated])
|
||||
|
||||
//browsers disagree here
|
||||
try {
|
||||
|
||||
o(updated.dom.attributes.style.value).equals("")
|
||||
|
||||
} catch (e) {
|
||||
|
||||
o("style" in updated.dom.attributes).equals(false)
|
||||
|
||||
}
|
||||
})
|
||||
o("not setting style removes all styles", function() {
|
||||
var vnode = {"tag": "p", attrs: {style: "background-color: red"}}
|
||||
var updated = {"tag": "p", attrs: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
o("style" in vnode.dom.attributes).equals(true)
|
||||
o(vnode.dom.attributes.style.value).equals("background-color: red;")
|
||||
|
||||
render(root, [updated])
|
||||
|
||||
//browsers disagree here
|
||||
try {
|
||||
|
||||
o(updated.dom.attributes.style.value).equals("")
|
||||
|
||||
} catch (e) {
|
||||
|
||||
o("style" in updated.dom.attributes).equals(false)
|
||||
|
||||
}
|
||||
})
|
||||
o("replaces el", function() {
|
||||
var vnode = {tag: "a"}
|
||||
var updated = {tag: "b"}
|
||||
|
|
@ -224,7 +303,7 @@ o.spec("updateElement", function() {
|
|||
|
||||
o(updated.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
o("restores correctly when recycling", function() {
|
||||
o("doesn't restore since we're not recycling", function() {
|
||||
var vnode = {tag: "div", key: 1}
|
||||
var updated = {tag: "div", key: 2}
|
||||
|
||||
|
|
@ -237,9 +316,9 @@ o.spec("updateElement", function() {
|
|||
var c = vnode.dom
|
||||
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(a).equals(c)
|
||||
o(a).notEquals(c) // this used to be a recycling pool test
|
||||
})
|
||||
o("restores correctly when recycling via map", function() {
|
||||
o("doesn't restore since we're not recycling (via map)", function() {
|
||||
var a = {tag: "div", key: 1}
|
||||
var b = {tag: "div", key: 2}
|
||||
var c = {tag: "div", key: 3}
|
||||
|
|
@ -256,6 +335,6 @@ o.spec("updateElement", function() {
|
|||
var y = root.childNodes[1]
|
||||
|
||||
o(root.childNodes.length).equals(3)
|
||||
o(x).equals(y)
|
||||
o(x).notEquals(y) // this used to be a recycling pool test
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -134,17 +134,17 @@ o.spec("updateNodes", function() {
|
|||
o("reverses els w/ odd count", function() {
|
||||
var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}]
|
||||
var updated = [{tag: "i", key: 3}, {tag: "b", key: 2}, {tag: "a", key: 1}]
|
||||
|
||||
var expectedTags = updated.map(function(vn) {return vn.tag})
|
||||
render(root, vnodes)
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.childNodes.length).equals(3)
|
||||
o(updated[0].dom.nodeName).equals("I")
|
||||
o(updated[0].dom).equals(root.childNodes[0])
|
||||
o(updated[1].dom.nodeName).equals("B")
|
||||
o(updated[1].dom).equals(root.childNodes[1])
|
||||
o(updated[2].dom.nodeName).equals("A")
|
||||
o(updated[2].dom).equals(root.childNodes[2])
|
||||
o(tagNames).deepEquals(expectedTags)
|
||||
})
|
||||
o("creates el at start", function() {
|
||||
var vnodes = [{tag: "a", key: 1}]
|
||||
|
|
@ -264,6 +264,21 @@ o.spec("updateNodes", function() {
|
|||
o(updated[2].dom.nodeName).equals("S")
|
||||
o(updated[2].dom).equals(root.childNodes[2])
|
||||
})
|
||||
o("creates, deletes, reverses els at same time with '__proto__' key", function() {
|
||||
var vnodes = [{tag: "a", key: "__proto__"}, {tag: "i", key: 3}, {tag: "b", key: 2}]
|
||||
var updated = [{tag: "b", key: 2}, {tag: "a", key: "__proto__"}, {tag: "s", key: 4}]
|
||||
|
||||
render(root, vnodes)
|
||||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(3)
|
||||
o(updated[0].dom.nodeName).equals("B")
|
||||
o(updated[0].dom).equals(root.childNodes[0])
|
||||
o(updated[1].dom.nodeName).equals("A")
|
||||
o(updated[1].dom).equals(root.childNodes[1])
|
||||
o(updated[2].dom.nodeName).equals("S")
|
||||
o(updated[2].dom).equals(root.childNodes[2])
|
||||
})
|
||||
o("adds to empty array followed by el", function() {
|
||||
var vnodes = [{tag: "[", key: 1, children: []}, {tag: "b", key: 2}]
|
||||
var updated = [{tag: "[", key: 1, children: [{tag: "a"}]}, {tag: "b", key: 2}]
|
||||
|
|
@ -614,6 +629,38 @@ o.spec("updateNodes", function() {
|
|||
o(updated[0].dom.nodeName).equals("I")
|
||||
o(updated[0].dom).equals(root.childNodes[0])
|
||||
})
|
||||
o("cached keyed nodes move when the list is reversed", function(){
|
||||
var a = {tag: "a", key: "a"}
|
||||
var b = {tag: "b", key: "b"}
|
||||
var c = {tag: "c", key: "c"}
|
||||
var d = {tag: "d", key: "d"}
|
||||
|
||||
render(root, [a, b, c, d])
|
||||
render(root, [d, c, b, a])
|
||||
|
||||
o(root.childNodes.length).equals(4)
|
||||
o(root.childNodes[0].nodeName).equals("D")
|
||||
o(root.childNodes[1].nodeName).equals("C")
|
||||
o(root.childNodes[2].nodeName).equals("B")
|
||||
o(root.childNodes[3].nodeName).equals("A")
|
||||
})
|
||||
o("cached keyed nodes move when diffed via the map", function() {
|
||||
var onupdate = o.spy()
|
||||
var a = {tag: "a", key: "a", attrs: {onupdate: onupdate}}
|
||||
var b = {tag: "b", key: "b", attrs: {onupdate: onupdate}}
|
||||
var c = {tag: "c", key: "c", attrs: {onupdate: onupdate}}
|
||||
var d = {tag: "d", key: "d", attrs: {onupdate: onupdate}}
|
||||
|
||||
render(root, [a, b, c, d])
|
||||
render(root, [b, d, a, c])
|
||||
|
||||
o(root.childNodes.length).equals(4)
|
||||
o(root.childNodes[0].nodeName).equals("B")
|
||||
o(root.childNodes[1].nodeName).equals("D")
|
||||
o(root.childNodes[2].nodeName).equals("A")
|
||||
o(root.childNodes[3].nodeName).equals("C")
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
o("removes then create different bigger", function() {
|
||||
var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}]
|
||||
var temp = []
|
||||
|
|
@ -776,7 +823,7 @@ o.spec("updateNodes", function() {
|
|||
o(root.childNodes[0].childNodes[1].childNodes.length).equals(1)
|
||||
o(root.childNodes[1].childNodes.length).equals(0)
|
||||
})
|
||||
o("recycles", function() {
|
||||
o("doesn't recycle", function() {
|
||||
var vnodes = [{tag: "div", key: 1}]
|
||||
var temp = []
|
||||
var updated = [{tag: "div", key: 1}]
|
||||
|
|
@ -785,10 +832,10 @@ o.spec("updateNodes", function() {
|
|||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom).equals(updated[0].dom)
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(updated[0].dom.nodeName).equals("DIV")
|
||||
})
|
||||
o("recycles when not keyed", function() {
|
||||
o("doesn't recycle when not keyed", function() {
|
||||
var vnodes = [{tag: "div"}]
|
||||
var temp = []
|
||||
var updated = [{tag: "div"}]
|
||||
|
|
@ -798,19 +845,22 @@ o.spec("updateNodes", function() {
|
|||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(vnodes[0].dom).equals(updated[0].dom)
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(updated[0].dom.nodeName).equals("DIV")
|
||||
})
|
||||
o("recycles deep", function() {
|
||||
o("doesn't recycle deep", function() {
|
||||
var vnodes = [{tag: "div", children: [{tag: "a", key: 1}]}]
|
||||
var temp = [{tag: "div"}]
|
||||
var updated = [{tag: "div", children: [{tag: "a", key: 1}]}]
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
var oldChild = vnodes[0].dom.firstChild
|
||||
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom.firstChild).equals(updated[0].dom.firstChild)
|
||||
o(oldChild).notEquals(updated[0].dom.firstChild) // this used to be a recycling pool test
|
||||
o(updated[0].dom.firstChild.nodeName).equals("A")
|
||||
})
|
||||
o("mixed unkeyed tags are not broken by recycle", function() {
|
||||
|
|
@ -839,6 +889,19 @@ o.spec("updateNodes", function() {
|
|||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
o("onremove doesn't fire from nodes in the pool (#1990)", function () {
|
||||
var onremove = o.spy()
|
||||
render(root, [
|
||||
{tag: "div", children: [{tag: "div", attrs: {onremove: onremove}}]},
|
||||
{tag: "div", children: [{tag: "div", attrs: {onremove: onremove}}]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "div", children: [{tag: "div", attrs: {onremove: onremove}}]}
|
||||
])
|
||||
render(root,[])
|
||||
|
||||
o(onremove.callCount).equals(2)
|
||||
})
|
||||
o("cached, non-keyed nodes skip diff", function () {
|
||||
var onupdate = o.spy();
|
||||
var cached = {tag:"a", attrs:{onupdate: onupdate}}
|
||||
|
|
@ -857,6 +920,72 @@ o.spec("updateNodes", function() {
|
|||
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
o("keyed cached elements are re-initialized when brought back from the pool (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", key: 1, children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("unkeyed cached elements are re-initialized when brought back from the pool (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("keyed cached elements are re-initialized when brought back from nested pools (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", key: 1, children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [{tag: "div", children: []}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("unkeyed cached elements are re-initialized when brought back from nested pools (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [{tag: "div", children: []}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("null stays in place", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
|
|
@ -904,6 +1033,231 @@ o.spec("updateNodes", function() {
|
|||
|
||||
o(vnode.dom).notEquals(updated.dom)
|
||||
})
|
||||
o("don't add back elements from fragments that are restored from the pool #1991", function() {
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: []}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: [{tag: "div"}]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: [null]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: []}
|
||||
])
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
o("don't add back elements from fragments that are being removed #1991", function() {
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "p"},
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: [{tag: "div", text: 5}]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: []}
|
||||
])
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
o("handles null values in unkeyed lists of different length (#2003)", function() {
|
||||
var oncreate = o.spy();
|
||||
var onremove = o.spy();
|
||||
var onupdate = o.spy();
|
||||
function attrs() {
|
||||
return {oncreate: oncreate, onremove: onremove, onupdate: onupdate}
|
||||
}
|
||||
|
||||
render(root, [{tag: "div", attrs: attrs()}, null]);
|
||||
render(root, [null, {tag: "div", attrs: attrs()}, null]);
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onremove.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
o("supports changing the element of a keyed element in a list when traversed bottom-up", function() {
|
||||
try {
|
||||
render(root, [{tag: "a", key: 2}])
|
||||
render(root, [{tag: "b", key: 1}, {tag: "b", key: 2}])
|
||||
|
||||
o(root.childNodes.length).equals(2)
|
||||
o(root.childNodes[0].nodeName).equals("B")
|
||||
o(root.childNodes[1].nodeName).equals("B")
|
||||
} catch (e) {
|
||||
o(e).equals(null)
|
||||
}
|
||||
})
|
||||
o("supports changing the element of a keyed element in a list when looking up nodes using the map", function() {
|
||||
try {
|
||||
render(root, [{tag: "x", key: 1}, {tag: "y", key: 2}, {tag: "z", key: 3}])
|
||||
render(root, [{tag: "b", key: 2}, {tag: "c", key: 1}, {tag: "d", key: 4}, {tag: "e", key: 3}])
|
||||
|
||||
o(root.childNodes.length).equals(4)
|
||||
o(root.childNodes[0].nodeName).equals("B")
|
||||
o(root.childNodes[1].nodeName).equals("C")
|
||||
o(root.childNodes[2].nodeName).equals("D")
|
||||
o(root.childNodes[3].nodeName).equals("E")
|
||||
} catch (e) {
|
||||
o(e).equals(null)
|
||||
}
|
||||
})
|
||||
o("don't fetch the nextSibling from the pool", function() {
|
||||
render(root, [{tag: "[", children: [{tag: "div", key: 1}, {tag: "div", key: 2}]}, {tag: "p"}])
|
||||
render(root, [{tag: "[", children: []}, {tag: "p"}])
|
||||
render(root, [{tag: "[", children: [{tag: "div", key: 2}, {tag: "div", key: 1}]}, {tag: "p"}])
|
||||
|
||||
o([].map.call(root.childNodes, function(el) {return el.nodeName})).deepEquals(["DIV", "DIV", "P"])
|
||||
})
|
||||
o("minimizes DOM operations when scrambling a keyed lists", function() {
|
||||
var vnodes = [{tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}]
|
||||
var updated = [{tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "d", key: "d"}, {tag: "c", key: "c"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an odd number of items", function() {
|
||||
var vnodes = [{tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}]
|
||||
var updated = [{tag: "d", key: "d"}, {tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(3)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an even number of items", function() {
|
||||
var vnodes = [{tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}]
|
||||
var updated = [{tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when scrambling a keyed lists with prefixes and suffixes", function() {
|
||||
var vnodes = [{tag: "i", key: "i"}, {tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}, {tag: "j", key: "j"}]
|
||||
var updated = [{tag: "i", key: "i"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "d", key: "d"}, {tag: "c", key: "c"}, {tag: "j", key: "j"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an odd number of items with prefixes and suffixes", function() {
|
||||
var vnodes = [{tag: "i", key: "i"}, {tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}, {tag: "j", key: "j"}]
|
||||
var updated = [{tag: "i", key: "i"}, {tag: "d", key: "d"}, {tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "j", key: "j"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(3)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an even number of items with prefixes and suffixes", function() {
|
||||
var vnodes = [{tag: "i", key: "i"}, {tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "j", key: "j"}]
|
||||
var updated = [{tag: "i", key: "i"}, {tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "j", key: "j"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("scrambling sample 1", function() {
|
||||
function vnodify(str) {
|
||||
return str.split(",").map(function(k) {return {tag: k, key: k}})
|
||||
}
|
||||
var vnodes = vnodify("k0,k1,k2,k3,k4,k5,k6,k7,k8,k9")
|
||||
var updated = vnodify("k4,k1,k2,k9,k0,k3,k6,k5,k8,k7")
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(5)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("scrambling sample 2", function() {
|
||||
function vnodify(str) {
|
||||
return str.split(",").map(function(k) {return {tag: k, key: k}})
|
||||
}
|
||||
var vnodes = vnodify("k0,k1,k2,k3,k4,k5,k6,k7,k8,k9")
|
||||
var updated = vnodify("b,d,k1,k0,k2,k3,k4,a,c,k5,k6,k7,k8,k9")
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(5)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
|
|
@ -940,6 +1294,19 @@ o.spec("updateNodes", function() {
|
|||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("S")
|
||||
})
|
||||
o("removing a component that returns a fragment doesn't throw (regression test for incidental bug introduced while debugging some Flems)", function() {
|
||||
var component = createComponent({
|
||||
view: function() {return {tag: "[", children:[{tag: "a"}, {tag: "b"}]}}
|
||||
})
|
||||
try {
|
||||
render(root, [{tag: component}])
|
||||
render(root, [])
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
} catch (e) {
|
||||
o(e).equals(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
157
render/tests/test-updateNodesFuzzer.js
Normal file
157
render/tests/test-updateNodesFuzzer.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
// pilfered and adapted from https://github.com/domvm/domvm/blob/7aaec609e4c625b9acf9a22d035d6252a5ca654f/test/src/flat-list-keyed-fuzz.js
|
||||
o.spec("updateNodes keyed list Fuzzer", function() {
|
||||
var i = 0, $window, root, render
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
root = $window.document.createElement("div")
|
||||
render = vdom($window).render
|
||||
})
|
||||
|
||||
|
||||
void [
|
||||
{delMax: 0, movMax: 50, insMax: 9},
|
||||
{delMax: 3, movMax: 5, insMax: 5},
|
||||
{delMax: 7, movMax: 15, insMax: 0},
|
||||
{delMax: 5, movMax: 100, insMax: 3},
|
||||
{delMax: 5, movMax: 0, insMax: 3},
|
||||
].forEach(function(c) {
|
||||
var tests = 250
|
||||
|
||||
while (tests--) {
|
||||
var test = fuzzTest(c.delMax, c.movMax, c.insMax)
|
||||
o(i++ + ": " + test.list.join() + " -> " + test.updated.join(), function() {
|
||||
render(root, test.list.map(function(x){return {tag: x, key: x}}))
|
||||
addSpies(root)
|
||||
render(root, test.updated.map(function(x){return {tag: x, key: x}}))
|
||||
|
||||
if (root.appendChild.callCount + root.insertBefore.callCount !== test.expected.creations + test.expected.moves) console.log(test, {aC: root.appendChild.callCount, iB: root.insertBefore.callCount}, [].map.call(root.childNodes, function(n){return n.nodeName.toLowerCase()}))
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(test.expected.creations + test.expected.moves)("moves")
|
||||
o(root.removeChild.callCount).equals(test.expected.deletions)("deletions")
|
||||
o([].map.call(root.childNodes, function(n){return n.nodeName.toLowerCase()})).deepEquals(test.updated)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
|
||||
// impl borrowed from https://github.com/ivijs/ivi
|
||||
function longestIncreasingSubsequence(a) {
|
||||
var p = a.slice()
|
||||
var result = []
|
||||
result.push(0)
|
||||
var u
|
||||
var v
|
||||
|
||||
for (var i = 0, il = a.length; i < il; ++i) {
|
||||
var j = result[result.length - 1]
|
||||
if (a[j] < a[i]) {
|
||||
p[i] = j
|
||||
result.push(i)
|
||||
continue
|
||||
}
|
||||
|
||||
u = 0
|
||||
v = result.length - 1
|
||||
|
||||
while (u < v) {
|
||||
var c = ((u + v) / 2) | 0 // eslint-disable-line no-bitwise
|
||||
if (a[result[c]] < a[i]) {
|
||||
u = c + 1
|
||||
} else {
|
||||
v = c
|
||||
}
|
||||
}
|
||||
|
||||
if (a[i] < a[result[u]]) {
|
||||
if (u > 0) {
|
||||
p[i] = result[u - 1]
|
||||
}
|
||||
result[u] = i
|
||||
}
|
||||
}
|
||||
|
||||
u = result.length
|
||||
v = result[u - 1]
|
||||
|
||||
while (u-- > 0) {
|
||||
result[u] = v
|
||||
v = p[v]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function rand(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
function ins(arr, qty) {
|
||||
var p = ["a","b","c","d","e","f","g","h","i"]
|
||||
|
||||
while (qty-- > 0)
|
||||
arr.splice(rand(0, arr.length - 1), 0, p.shift())
|
||||
}
|
||||
|
||||
function del(arr, qty) {
|
||||
while (qty-- > 0)
|
||||
arr.splice(rand(0, arr.length - 1), 1)
|
||||
}
|
||||
|
||||
function mov(arr, qty) {
|
||||
while (qty-- > 0) {
|
||||
var from = rand(0, arr.length - 1)
|
||||
var to = rand(0, arr.length - 1)
|
||||
|
||||
arr.splice(to, 0, arr.splice(from, 1)[0])
|
||||
}
|
||||
}
|
||||
|
||||
function fuzzTest(delMax, movMax, insMax) {
|
||||
var list = ["k0","k1","k2","k3","k4","k5","k6","k7","k8","k9"]
|
||||
var copy = list.slice()
|
||||
|
||||
var delCount = rand(0, delMax),
|
||||
movCount = rand(0, movMax),
|
||||
insCount = rand(0, insMax)
|
||||
|
||||
del(copy, delCount)
|
||||
mov(copy, movCount)
|
||||
|
||||
var expected = {
|
||||
creations: insCount,
|
||||
deletions: delCount,
|
||||
moves: 0
|
||||
}
|
||||
|
||||
if (movCount > 0) {
|
||||
var newPos = copy.map(function(v) {
|
||||
return list.indexOf(v)
|
||||
}).filter(function(i) {
|
||||
return i != -1
|
||||
})
|
||||
var lis = longestIncreasingSubsequence(newPos)
|
||||
expected.moves = copy.length - lis.length
|
||||
}
|
||||
|
||||
ins(copy, insCount)
|
||||
|
||||
return {
|
||||
expected: expected,
|
||||
list: list,
|
||||
updated: copy
|
||||
}
|
||||
}
|
||||
|
||||
function addSpies(node) {
|
||||
node.appendChild = o.spy(node.appendChild)
|
||||
node.insertBefore = o.spy(node.insertBefore)
|
||||
node.removeChild = o.spy(node.removeChild)
|
||||
}
|
||||
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
"use strict"
|
||||
|
||||
function Vnode(tag, key, attrs, children, text, dom) {
|
||||
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false}
|
||||
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined}
|
||||
}
|
||||
Vnode.normalize = function(node) {
|
||||
if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
|
||||
if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined)
|
||||
return node
|
||||
}
|
||||
Vnode.normalizeChildren = function normalizeChildren(children) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
children[i] = Vnode.normalize(children[i])
|
||||
Vnode.normalizeChildren = function normalizeChildren(input) {
|
||||
var children = []
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
children[i] = Vnode.normalize(input[i])
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ module.exports = function($window, Promise) {
|
|||
}
|
||||
if (args.withCredentials) xhr.withCredentials = args.withCredentials
|
||||
|
||||
if (args.timeout) xhr.timeout = args.timeout
|
||||
|
||||
if (args.responseType) xhr.responseType = args.responseType
|
||||
|
||||
for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) {
|
||||
xhr.setRequestHeader(key, args.headers[key])
|
||||
}
|
||||
|
|
@ -88,12 +92,13 @@ module.exports = function($window, Promise) {
|
|||
if (xhr.readyState === 4) {
|
||||
try {
|
||||
var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
|
||||
if (args.extract !== extract || (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
|
||||
resolve(cast(args.type, response))
|
||||
}
|
||||
else {
|
||||
var error = new Error(xhr.responseText)
|
||||
for (var key in response) error[key] = response[key]
|
||||
error.code = xhr.status
|
||||
error.response = response
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +164,7 @@ module.exports = function($window, Promise) {
|
|||
|
||||
function deserialize(data) {
|
||||
try {return data !== "" ? JSON.parse(data) : null}
|
||||
catch (e) {throw new Error(data)}
|
||||
catch (e) {throw new Error("Invalid JSON: " + data)}
|
||||
}
|
||||
|
||||
function extract(xhr) {return xhr.responseText}
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
xhr({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) {
|
||||
o(data).equals('{"test":123}')
|
||||
o(data).equals("{\"test\":123}")
|
||||
}).then(done)
|
||||
})
|
||||
o("deserialize parameter works in POST", function(done) {
|
||||
|
|
@ -244,7 +244,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
xhr({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) {
|
||||
o(data).equals('{"test":123}')
|
||||
o(data).equals("{\"test\":123}")
|
||||
}).then(done)
|
||||
})
|
||||
o("extract parameter works in GET", function(done) {
|
||||
|
|
@ -258,7 +258,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
xhr({method: "GET", url: "/item", extract: extract}).then(function(data) {
|
||||
o(data).equals('{"test":123}')
|
||||
o(data).equals("{\"test\":123}")
|
||||
}).then(done)
|
||||
})
|
||||
o("extract parameter works in POST", function(done) {
|
||||
|
|
@ -272,7 +272,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
xhr({method: "POST", url: "/item", extract: extract}).then(function(data) {
|
||||
o(data).equals('{"test":123}')
|
||||
o(data).equals("{\"test\":123}")
|
||||
}).then(done)
|
||||
})
|
||||
o("ignores deserialize if extract is defined", function(done) {
|
||||
|
|
@ -435,6 +435,34 @@ o.spec("xhr", function() {
|
|||
done()
|
||||
})
|
||||
})
|
||||
o("set timeout to xhr instance", function() {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
return xhr({
|
||||
method: "GET", url: "/item",
|
||||
timeout: 42,
|
||||
config: function(xhr) {
|
||||
o(xhr.timeout).equals(42)
|
||||
}
|
||||
})
|
||||
})
|
||||
o("set responseType to xhr instance", function() {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
return xhr({
|
||||
method: "GET", url: "/item",
|
||||
responseType: "blob",
|
||||
config: function(xhr) {
|
||||
o(xhr.responseType).equals("blob")
|
||||
}
|
||||
})
|
||||
})
|
||||
/*o("data maintains after interpolate", function() {
|
||||
mock.$defineRoutes({
|
||||
"PUT /items/:x": function() {
|
||||
|
|
@ -458,9 +486,10 @@ o.spec("xhr", function() {
|
|||
xhr({method: "GET", url: "/item"}).catch(function(e) {
|
||||
o(e instanceof Error).equals(true)
|
||||
o(e.message).equals(JSON.stringify({error: "error"}))
|
||||
o(e.code).equals(500)
|
||||
}).then(done)
|
||||
})
|
||||
o("extends Error with JSON response", function(done) {
|
||||
o("adds response to Error", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 500, responseText: JSON.stringify({message: "error", stack: "error on line 1"})}
|
||||
|
|
@ -468,8 +497,8 @@ o.spec("xhr", function() {
|
|||
})
|
||||
xhr({method: "GET", url: "/item"}).catch(function(e) {
|
||||
o(e instanceof Error).equals(true)
|
||||
o(e.message).equals("error")
|
||||
o(e.stack).equals("error on line 1")
|
||||
o(e.response.message).equals("error")
|
||||
o(e.response.stack).equals("error on line 1")
|
||||
}).then(done)
|
||||
})
|
||||
o("rejects on non-JSON server error", function(done) {
|
||||
|
|
@ -479,7 +508,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
xhr({method: "GET", url: "/item"}).catch(function(e) {
|
||||
o(e.message).equals("error")
|
||||
o(e.message).equals("Invalid JSON: error")
|
||||
}).then(done)
|
||||
})
|
||||
o("triggers all branched catches upon rejection", function(done) {
|
||||
|
|
@ -518,5 +547,35 @@ o.spec("xhr", function() {
|
|||
o(e instanceof Error).equals(true)
|
||||
}).then(done)
|
||||
})
|
||||
o("does not reject on status error code when extract provided", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 500, responseText: JSON.stringify({message: "error"})}
|
||||
}
|
||||
})
|
||||
xhr({
|
||||
method: "GET", url: "/item",
|
||||
extract: function(xhr) {return JSON.parse(xhr.responseText)}
|
||||
}).then(function(data) {
|
||||
o(data.message).equals("error")
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("rejects on error in extract", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({a: 1})}
|
||||
}
|
||||
})
|
||||
xhr({
|
||||
method: "GET", url: "/item",
|
||||
extract: function() {throw new Error("error")}
|
||||
}).catch(function(e) {
|
||||
o(e instanceof Error).equals(true)
|
||||
o(e.message).equals("error")
|
||||
}).then(function() {
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
7
stream/change-log.md
Normal file
7
stream/change-log.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Change log for stream
|
||||
|
||||
## 2.0.0
|
||||
- stream: Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150)
|
||||
|
||||
## 1.1.0
|
||||
- stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893))
|
||||
|
|
@ -19,7 +19,7 @@ function initStream(stream) {
|
|||
stream.constructor = createStream
|
||||
stream._state = {id: guid++, value: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], endStream: undefined, unregister: undefined}
|
||||
stream.map = stream["fantasy-land/map"] = map, stream["fantasy-land/ap"] = ap, stream["fantasy-land/of"] = createStream
|
||||
stream.valueOf = valueOf, stream.toJSON = toJSON, stream.toString = valueOf
|
||||
stream.toJSON = toJSON
|
||||
|
||||
Object.defineProperties(stream, {
|
||||
end: {get: function() {
|
||||
|
|
@ -53,7 +53,7 @@ function updateDependency(stream, mustSync) {
|
|||
var state = stream._state, parents = state.parents
|
||||
if (parents.length > 0 && parents.every(active) && (mustSync || parents.some(changed))) {
|
||||
var value = stream._state.derive()
|
||||
if (value === HALT) return false
|
||||
if (value === HALT) return unregisterStream(stream)
|
||||
updateState(stream, value)
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,6 @@ function unregisterStream(stream) {
|
|||
|
||||
function map(fn) {return combine(function(stream) {return fn(stream())}, [this])}
|
||||
function ap(stream) {return combine(function(s1, s2) {return s1()(s2())}, [stream, this])}
|
||||
function valueOf() {return this._state.value}
|
||||
function toJSON() {return this._state.value != null && typeof this._state.value.toJSON === "function" ? this._state.value.toJSON() : this._state.value}
|
||||
|
||||
function valid(stream) {return stream._state }
|
||||
|
|
@ -117,7 +116,9 @@ function merge(streams) {
|
|||
|
||||
function scan(reducer, seed, stream) {
|
||||
var newStream = combine(function (s) {
|
||||
return seed = reducer(seed, s._state.value)
|
||||
var next = reducer(seed, s._state.value)
|
||||
if (next !== HALT) return seed = next
|
||||
return HALT
|
||||
}, [stream])
|
||||
|
||||
if (newStream._state.state === 0) newStream(seed)
|
||||
|
|
|
|||
|
|
@ -8,11 +8,8 @@
|
|||
<script src="../../ospec/ospec.js"></script>
|
||||
<script src="../../test-utils/callAsync.js"></script>
|
||||
<script src="../stream.js"></script>
|
||||
<script src="../scan.js"></script>
|
||||
<script src="../scanMerge.js"></script>
|
||||
<script src="test-stream.js"></script>
|
||||
<script src="test-scanMerge.js"></script>
|
||||
<script src="test-scanMerge.js"></script>
|
||||
|
||||
<script>require("../../ospec/ospec").run()</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -30,4 +30,36 @@ o.spec("scan", function() {
|
|||
o(result[2]).equals(undefined)
|
||||
o(result[3]).deepEquals({a: 1})
|
||||
})
|
||||
|
||||
o("reducer can return HALT to prevent child updates", function() {
|
||||
var count = 0
|
||||
var action = stream()
|
||||
var store = stream.scan(function (arr, value) {
|
||||
switch (typeof value) {
|
||||
case "number":
|
||||
return arr.concat(value)
|
||||
default:
|
||||
return stream.HALT
|
||||
}
|
||||
}, [], action)
|
||||
var child = store.map(function (p) {
|
||||
count++
|
||||
return p
|
||||
})
|
||||
var result
|
||||
|
||||
action(7)
|
||||
action("11")
|
||||
action(undefined)
|
||||
action({a: 1})
|
||||
|
||||
result = child()
|
||||
|
||||
// check we got the expect result
|
||||
o(result[0]).equals(7)
|
||||
|
||||
// check child received minimum # of updates
|
||||
o(count).equals(2)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -164,6 +164,27 @@ o.spec("stream", function() {
|
|||
o(b()).equals(undefined)
|
||||
o(count).equals(0)
|
||||
})
|
||||
o("combine can conditionaly halt", function() {
|
||||
var count = 0
|
||||
var halt = false
|
||||
var a = Stream(1)
|
||||
var b = Stream.combine(function(a) {
|
||||
if (halt) {
|
||||
return Stream.HALT
|
||||
}
|
||||
return a()
|
||||
}, [a])["fantasy-land/map"](function(a) {
|
||||
count++
|
||||
return a
|
||||
})
|
||||
o(b()).equals(1)
|
||||
o(count).equals(1)
|
||||
halt = true
|
||||
count = 0
|
||||
a(2)
|
||||
o(b()).equals(1)
|
||||
o(count).equals(0)
|
||||
})
|
||||
o("combine will throw with a helpful error if given non-stream values", function () {
|
||||
var spy = o.spy()
|
||||
var a = Stream(1)
|
||||
|
|
@ -276,31 +297,6 @@ o.spec("stream", function() {
|
|||
o(spy.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
o.spec("valueOf", function() {
|
||||
o("works", function() {
|
||||
o(Stream(1).valueOf()).equals(1)
|
||||
o(Stream("a").valueOf()).equals("a")
|
||||
o(Stream(true).valueOf()).equals(true)
|
||||
o(Stream(null).valueOf()).equals(null)
|
||||
o(Stream(undefined).valueOf()).equals(undefined)
|
||||
o(Stream({a: 1}).valueOf()).deepEquals({a: 1})
|
||||
o(Stream([1, 2, 3]).valueOf()).deepEquals([1, 2, 3])
|
||||
o(Stream().valueOf()).equals(undefined)
|
||||
})
|
||||
o("allows implicit value access in mathematical operations", function() {
|
||||
o(Stream(1) + Stream(1)).equals(2)
|
||||
})
|
||||
})
|
||||
o.spec("toString", function() {
|
||||
o("aliases valueOf", function() {
|
||||
var stream = Stream(1)
|
||||
|
||||
o(stream.toString).equals(stream.valueOf)
|
||||
})
|
||||
o("allows implicit value access in string operations", function() {
|
||||
o(Stream("a") + Stream("b")).equals("ab")
|
||||
})
|
||||
})
|
||||
o.spec("toJSON", function() {
|
||||
o("works", function() {
|
||||
o(Stream(1).toJSON()).equals(1)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/*
|
||||
Known limitations:
|
||||
|
||||
- the innerHTML setter and the DOMParser only support a small subset of the true HTML/XML syntax.
|
||||
- `option.selected` can't be set/read when the option doesn't have a `select` parent
|
||||
- `element.attributes` is just a map of attribute names => Attr objects stubs
|
||||
- ...
|
||||
|
|
@ -18,6 +18,9 @@ module.exports = function(options) {
|
|||
options = options || {}
|
||||
var spy = options.spy || function(f){return f}
|
||||
var spymap = []
|
||||
|
||||
var hasOwn = ({}.hasOwnProperty)
|
||||
|
||||
function registerSpies(element, spies) {
|
||||
if(options.spy) {
|
||||
var i = spymap.indexOf(element)
|
||||
|
|
@ -37,6 +40,30 @@ module.exports = function(options) {
|
|||
function isModernEvent(type) {
|
||||
return type === "transitionstart" || type === "transitionend" || type === "animationstart" || type === "animationend"
|
||||
}
|
||||
function dispatchEvent(e) {
|
||||
var stopped = false
|
||||
e.stopImmediatePropagation = function() {
|
||||
e.stopPropagation()
|
||||
stopped = true
|
||||
}
|
||||
e.currentTarget = this
|
||||
if (this._events[e.type] != null) {
|
||||
for (var i = 0; i < this._events[e.type].handlers.length; i++) {
|
||||
var useCapture = this._events[e.type].options[i].capture
|
||||
if (useCapture && e.eventPhase < 3 || !useCapture && e.eventPhase > 1) {
|
||||
var handler = this._events[e.type].handlers[i]
|
||||
if (typeof handler === "function") try {handler.call(this, e)} catch(e) {setTimeout(function(){throw e})}
|
||||
else try {handler.handleEvent(e)} catch(e) {setTimeout(function(){throw e})}
|
||||
if (stopped) return
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is inaccurate. Normally the event fires in definition order, including legacy events
|
||||
// this would require getters/setters for each of them though and we haven't gotten around to
|
||||
// adding them since it would be at a high perf cost or would entail some heavy refactoring of
|
||||
// the mocks (prototypes instead of closures).
|
||||
if (e.eventPhase > 1 && typeof this["on" + e.type] === "function" && !isModernEvent(e.type)) try {this["on" + e.type](e)} catch(e) {setTimeout(function(){throw e})}
|
||||
}
|
||||
function appendChild(child) {
|
||||
var ancestor = this
|
||||
while (ancestor !== child && ancestor !== null) ancestor = ancestor.parentNode
|
||||
|
|
@ -47,7 +74,7 @@ module.exports = function(options) {
|
|||
var index = this.childNodes.indexOf(child)
|
||||
if (index > -1) this.childNodes.splice(index, 1)
|
||||
if (child.nodeType === 11) {
|
||||
while (child.firstChild != null) this.appendChild(child.firstChild)
|
||||
while (child.firstChild != null) appendChild.call(this, child.firstChild)
|
||||
child.childNodes = []
|
||||
}
|
||||
else {
|
||||
|
|
@ -75,8 +102,9 @@ module.exports = function(options) {
|
|||
var index = this.childNodes.indexOf(child)
|
||||
if (reference !== null && refIndex < 0) throw new TypeError("Invalid argument")
|
||||
if (index > -1) this.childNodes.splice(index, 1)
|
||||
if (reference === null) this.appendChild(child)
|
||||
if (reference === null) appendChild.call(this, child)
|
||||
else {
|
||||
if (index !== -1 && refIndex > index) refIndex--
|
||||
if (child.nodeType === 11) {
|
||||
this.childNodes.splice.apply(this.childNodes, [refIndex, 0].concat(child.childNodes))
|
||||
while (child.firstChild) {
|
||||
|
|
@ -102,9 +130,8 @@ module.exports = function(options) {
|
|||
// this is the correct kind of conversion, passing a Symbol throws in browsers too.
|
||||
var nodeValue = "" + value
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
|
||||
this.attributes[name] = {
|
||||
namespaceURI: null,
|
||||
namespaceURI: hasOwn.call(this.attributes, name) ? this.attributes[name].namespaceURI : null,
|
||||
get value() {return nodeValue},
|
||||
set value(value) {
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
|
|
@ -159,9 +186,43 @@ module.exports = function(options) {
|
|||
res.unshift(declList)
|
||||
return res
|
||||
}
|
||||
|
||||
function parseMarkup(value, root, voidElements, xmlns) {
|
||||
var depth = 0, stack = [root]
|
||||
value.replace(/<([a-z0-9\-]+?)((?:\s+?[^=]+?=(?:"[^"]*?"|'[^']*?'|[^\s>]*))*?)(\s*\/)?>|<\/([a-z0-9\-]+?)>|([^<]+)/g, function(match, startTag, attrs, selfClosed, endTag, text) {
|
||||
if (startTag) {
|
||||
var element = xmlns == null ? $window.document.createElement(startTag) : $window.document.createElementNS(xmlns, startTag)
|
||||
attrs.replace(/\s+?([^=]+?)=(?:"([^"]*?)"|'([^']*?)'|([^\s>]*))/g, function(match, key, doubleQuoted, singleQuoted, unquoted) {
|
||||
var keyParts = key.split(":")
|
||||
var name = keyParts.pop()
|
||||
var ns = keyParts[0]
|
||||
var value = doubleQuoted || singleQuoted || unquoted || ""
|
||||
if (ns != null) element.setAttributeNS(ns, name, value)
|
||||
else element.setAttribute(name, value)
|
||||
})
|
||||
stack[depth].appendChild(element)
|
||||
if (!selfClosed && voidElements.indexOf(startTag.toLowerCase()) < 0) stack[++depth] = element
|
||||
}
|
||||
else if (endTag) {
|
||||
depth--
|
||||
}
|
||||
else if (text) {
|
||||
stack[depth].appendChild($window.document.createTextNode(text)) // FIXME handle html entities
|
||||
}
|
||||
})
|
||||
}
|
||||
function DOMParser() {}
|
||||
DOMParser.prototype.parseFromString = function(src, mime) {
|
||||
if (mime !== "image/svg+xml") throw new Error("The DOMParser mock only supports the \"image/svg+xml\" MIME type")
|
||||
var match = src.match(/^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg">(.*)<\/svg>$/)
|
||||
if (!match) throw new Error("Please provide a bare SVG tag with the xmlns as only attribute")
|
||||
var value = match[1]
|
||||
var root = $window.document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
||||
parseMarkup(value, root, [], "http://www.w3.org/2000/svg")
|
||||
return {documentElement: root}
|
||||
}
|
||||
var activeElement
|
||||
var $window = {
|
||||
DOMParser: DOMParser,
|
||||
document: {
|
||||
createElement: function(tag) {
|
||||
var cssText = ""
|
||||
|
|
@ -186,7 +247,7 @@ module.exports = function(options) {
|
|||
}
|
||||
}
|
||||
}
|
||||
cssText = buf.join(" ")
|
||||
element.setAttribute("style", cssText = buf.join(" "))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -217,33 +278,21 @@ module.exports = function(options) {
|
|||
},
|
||||
set textContent(value) {
|
||||
this.childNodes = []
|
||||
if (value !== "") this.appendChild($window.document.createTextNode(value))
|
||||
if (value !== "") appendChild.call(this, $window.document.createTextNode(value))
|
||||
},
|
||||
set innerHTML(value) {
|
||||
var voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]
|
||||
while (this.firstChild) this.removeChild(this.firstChild)
|
||||
|
||||
var stack = [this], depth = 0, voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]
|
||||
value.replace(/<([a-z0-9\-]+?)((?:\s+?[^=]+?=(?:"[^"]*?"|'[^']*?'|[^\s>]*))*?)(\s*\/)?>|<\/([a-z0-9\-]+?)>|([^<]+)/g, function(match, startTag, attrs, selfClosed, endTag, text) {
|
||||
if (startTag) {
|
||||
var element = $window.document.createElement(startTag)
|
||||
attrs.replace(/\s+?([^=]+?)=(?:"([^"]*?)"|'([^']*?)'|([^\s>]*))/g, function(match, key, doubleQuoted, singleQuoted, unquoted) {
|
||||
var keyParts = key.split(":")
|
||||
var name = keyParts.pop()
|
||||
var ns = keyParts[0]
|
||||
var value = doubleQuoted || singleQuoted || unquoted || ""
|
||||
if (ns != null) element.setAttributeNS(ns, name, value)
|
||||
else element.setAttribute(name, value)
|
||||
})
|
||||
stack[depth].appendChild(element)
|
||||
if (!selfClosed && voidElements.indexOf(startTag.toLowerCase()) < 0) stack[++depth] = element
|
||||
}
|
||||
else if (endTag) {
|
||||
depth--
|
||||
}
|
||||
else if (text) {
|
||||
stack[depth].appendChild($window.document.createTextNode(text)) // FIXME handle html entities
|
||||
}
|
||||
})
|
||||
var match = value.match(/^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg">(.*)<\/svg>$/), root, ns
|
||||
if (match) {
|
||||
var value = match[1]
|
||||
root = $window.document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
||||
ns = "http://www.w3.org/2000/svg"
|
||||
appendChild.call(this, root)
|
||||
} else {
|
||||
root = this
|
||||
}
|
||||
parseMarkup(value, root, voidElements, ns)
|
||||
},
|
||||
get style() {
|
||||
return style
|
||||
|
|
@ -260,39 +309,121 @@ module.exports = function(options) {
|
|||
else this.setAttribute("class", value)
|
||||
},
|
||||
focus: function() {activeElement = this},
|
||||
addEventListener: function(type, callback) {
|
||||
if (events[type] == null) events[type] = [callback]
|
||||
else events[type].push(callback)
|
||||
addEventListener: function(type, handler, options) {
|
||||
if (arguments.length > 2) {
|
||||
if (typeof options === "object" && options != null) throw new TypeError("NYI: addEventListener options")
|
||||
else if (typeof options !== "boolean") throw new TypeError("boolean expected for useCapture")
|
||||
else options = {capture: options}
|
||||
} else {
|
||||
options = {capture: false}
|
||||
}
|
||||
if (events[type] == null) events[type] = {handlers: [handler], options: [options]}
|
||||
else {
|
||||
var found = false
|
||||
for (var i = 0; i < events[type].handlers.length; i++) {
|
||||
if (events[type].handlers[i] === handler && events[type].options[i].capture === options.capture) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
events[type].handlers.push(handler)
|
||||
events[type].options.push(options)
|
||||
}
|
||||
}
|
||||
},
|
||||
removeEventListener: function(type, callback) {
|
||||
removeEventListener: function(type, handler, options) {
|
||||
if (arguments.length > 2) {
|
||||
if (typeof options === "object" && options != null) throw new TypeError("NYI: addEventListener options")
|
||||
else if (typeof options !== "boolean") throw new TypeError("boolean expected for useCapture")
|
||||
else options = {capture: options}
|
||||
} else {
|
||||
options = {capture: false}
|
||||
}
|
||||
if (events[type] != null) {
|
||||
var index = events[type].indexOf(callback)
|
||||
if (index > -1) events[type].splice(index, 1)
|
||||
for (var i = 0; i < events[type].handlers.length; i++) {
|
||||
if (events[type].handlers[i] === handler && events[type].options[i].capture === options.capture) {
|
||||
events[type].handlers.splice(i, 1)
|
||||
events[type].options.splice(i, 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dispatchEvent: function(e) {
|
||||
if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].value === "checkbox" && e.type === "click") {
|
||||
this.checked = !this.checked
|
||||
var parents = []
|
||||
if (this.parentNode != null) {
|
||||
var parent = this.parentNode
|
||||
do {
|
||||
parents.push(parent)
|
||||
parent = parent.parentNode
|
||||
} while (parent != null)
|
||||
}
|
||||
|
||||
e.target = this
|
||||
if (events[e.type] != null) {
|
||||
for (var i = 0; i < events[e.type].length; i++) {
|
||||
events[e.type][i].call(this, e)
|
||||
var prevented = false
|
||||
e.preventDefault = function() {
|
||||
prevented = true
|
||||
}
|
||||
Object.defineProperty(e, "$defaultPrevented", {
|
||||
configurable: true,
|
||||
get: function () { return prevented }
|
||||
})
|
||||
var stopped = false
|
||||
e.stopPropagation = function() {
|
||||
stopped = true
|
||||
}
|
||||
Object.defineProperty(e, "$propagationStopped", {
|
||||
configurable: true,
|
||||
get: function () { return prevented }
|
||||
})
|
||||
e.eventPhase = 1
|
||||
try {
|
||||
for (var i = parents.length - 1; 0 <= i; i--) {
|
||||
dispatchEvent.call(parents[i], e)
|
||||
if (stopped) {
|
||||
return
|
||||
}
|
||||
}
|
||||
e.eventPhase = 2
|
||||
dispatchEvent.call(this, e)
|
||||
if (stopped) {
|
||||
return
|
||||
}
|
||||
e.eventPhase = 3
|
||||
for (var i = 0; i < parents.length; i++) {
|
||||
dispatchEvent.call(parents[i], e)
|
||||
if (stopped) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
throw e
|
||||
} finally {
|
||||
e.eventPhase = 0
|
||||
if (!prevented) {
|
||||
if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].value === "checkbox" && e.type === "click") {
|
||||
this.checked = !this.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
e.preventDefault = function() {
|
||||
// TODO: should this do something?
|
||||
}
|
||||
if (typeof this["on" + e.type] === "function" && !isModernEvent(e.type)) this["on" + e.type](e)
|
||||
|
||||
},
|
||||
onclick: null,
|
||||
_events: events
|
||||
}
|
||||
|
||||
if (element.nodeName === "A") {
|
||||
Object.defineProperty(element, "href", {
|
||||
get: function() {return this.attributes["href"] === undefined ? "" : "[FIXME implement]"},
|
||||
set: function(value) {this.setAttribute("href", value)},
|
||||
get: function() {
|
||||
if (this.namespaceURI === "http://www.w3.org/2000/svg") {
|
||||
var val = this.hasAttribute("href") ? this.attributes.href.value : ""
|
||||
return {baseVal: val, animVal: val}
|
||||
} else return this.attributes["href"] === undefined ? "" : "[FIXME implement]"
|
||||
},
|
||||
set: function(value) {
|
||||
// This is a readonly attribute for SVG, todo investigate MathML which may have yet another IDL
|
||||
if (this.namespaceURI !== "http://www.w3.org/2000/svg") this.setAttribute("href", value)
|
||||
},
|
||||
enumerable: true,
|
||||
})
|
||||
}
|
||||
|
|
@ -443,7 +574,7 @@ module.exports = function(options) {
|
|||
if (element.nodeName === "OPTION") {
|
||||
var valueSetter = spy(function(value) {
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
this.setAttribute("value", value === null ? "" : "" + value)
|
||||
this.setAttribute("value", "" + value)
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
})
|
||||
Object.defineProperty(element, "value", {
|
||||
|
|
@ -513,7 +644,8 @@ module.exports = function(options) {
|
|||
},
|
||||
createEvent: function() {
|
||||
return {
|
||||
initEvent: function(type) {this.type = type},
|
||||
eventPhase: 0,
|
||||
initEvent: function(type) {this.type = type}
|
||||
}
|
||||
},
|
||||
get activeElement() {return activeElement},
|
||||
|
|
|
|||
|
|
@ -7,21 +7,20 @@
|
|||
<script src="../../module/module.js"></script>
|
||||
<script src="../../ospec/ospec.js"></script>
|
||||
<script src="../../querystring/parse.js"></script>
|
||||
|
||||
<script src="../../test-utils/callAsync.js"></script>
|
||||
<script src="../../test-utils/parseURL.js"></script>
|
||||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
<script src="../../test-utils/xhrMock.js"></script>
|
||||
<script src="../../test-utils/domMock.js"></script>
|
||||
<script src="../../test-utils/browserMock.js"></script>
|
||||
<script src="../../test-utils/component.js"></script>
|
||||
<script src="../../test-utils/components.js"></script>
|
||||
<script src="test-callAsync.js"></script>
|
||||
<script src="test-parseURL.js"></script>
|
||||
<script src="test-pushStateMock.js"></script>
|
||||
<script src="test-xhrMock.js"></script>
|
||||
<script src="test-domMock.js"></script>
|
||||
<script src="test-browserMock.js"></script>
|
||||
<script src="test-component.js"></script>
|
||||
<script src="test-components.js"></script>
|
||||
|
||||
<script>require("../../ospec/ospec").run()</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ var o = require("../../ospec/ospec")
|
|||
var domMock = require("../../test-utils/domMock")
|
||||
|
||||
o.spec("domMock", function() {
|
||||
var $document
|
||||
var $document, $window
|
||||
o.beforeEach(function() {
|
||||
$document = domMock().document
|
||||
$window = domMock()
|
||||
$document = $window.document
|
||||
})
|
||||
|
||||
o.spec("createElement", function() {
|
||||
|
|
@ -241,6 +242,28 @@ o.spec("domMock", function() {
|
|||
o(a.parentNode).equals(parent)
|
||||
o(b.parentNode).equals(parent)
|
||||
})
|
||||
o("moves existing node forward but not at the end", function() {
|
||||
var parent = $document.createElement("div")
|
||||
var a = $document.createElement("a")
|
||||
var b = $document.createElement("b")
|
||||
var c = $document.createElement("c")
|
||||
parent.appendChild(a)
|
||||
parent.appendChild(b)
|
||||
parent.appendChild(c)
|
||||
parent.insertBefore(a, c)
|
||||
|
||||
o(parent.childNodes.length).equals(3)
|
||||
o(parent.childNodes[0]).equals(b)
|
||||
o(parent.childNodes[1]).equals(a)
|
||||
o(parent.childNodes[2]).equals(c)
|
||||
o(parent.firstChild).equals(b)
|
||||
o(parent.firstChild.nextSibling).equals(a)
|
||||
o(parent.firstChild.nextSibling.nextSibling).equals(c)
|
||||
o(a.parentNode).equals(parent)
|
||||
o(b.parentNode).equals(parent)
|
||||
o(c.parentNode).equals(parent)
|
||||
|
||||
})
|
||||
o("removes from old parent", function() {
|
||||
var parent = $document.createElement("div")
|
||||
var source = $document.createElement("span")
|
||||
|
|
@ -341,6 +364,12 @@ o.spec("domMock", function() {
|
|||
|
||||
o(div.getAttribute("id")).equals("aaa")
|
||||
})
|
||||
o("works for attributes with a namespace", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttributeNS("http://www.w3.org/1999/xlink", "href", "aaa")
|
||||
|
||||
o(div.getAttribute("href")).equals("aaa")
|
||||
})
|
||||
})
|
||||
|
||||
o.spec("setAttribute", function() {
|
||||
|
|
@ -406,18 +435,40 @@ o.spec("domMock", function() {
|
|||
|
||||
o.spec("setAttributeNS", function() {
|
||||
o("works", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttributeNS("http://www.w3.org/1999/xlink", "href", "aaa")
|
||||
var a = $document.createElementNS("http://www.w3.org/2000/svg", "a")
|
||||
a.setAttributeNS("http://www.w3.org/1999/xlink", "href", "/aaa")
|
||||
|
||||
o(div.attributes["href"].value).equals("aaa")
|
||||
o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
o(a.href).deepEquals({baseVal: "/aaa", animVal: "/aaa"})
|
||||
o(a.attributes["href"].value).equals("/aaa")
|
||||
o(a.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
})
|
||||
o("works w/ number", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttributeNS("http://www.w3.org/1999/xlink", "href", 123)
|
||||
var a = $document.createElementNS("http://www.w3.org/2000/svg", "a")
|
||||
a.setAttributeNS("http://www.w3.org/1999/xlink", "href", 123)
|
||||
|
||||
o(div.attributes["href"].value).equals("123")
|
||||
o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
o(a.href).deepEquals({baseVal: "123", animVal: "123"})
|
||||
o(a.attributes["href"].value).equals("123")
|
||||
o(a.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
})
|
||||
o("attributes with a namespace can be querried, updated and removed with non-NS functions", function() {
|
||||
var a = $document.createElementNS("http://www.w3.org/2000/svg", "a")
|
||||
a.setAttributeNS("http://www.w3.org/1999/xlink", "href", "/aaa")
|
||||
|
||||
o(a.hasAttribute("href")).equals(true)
|
||||
o(a.getAttribute("href")).equals("/aaa")
|
||||
|
||||
a.setAttribute("href", "/bbb")
|
||||
|
||||
o(a.href).deepEquals({baseVal: "/bbb", animVal: "/bbb"})
|
||||
o(a.getAttribute("href")).equals("/bbb")
|
||||
o(a.attributes["href"].value).equals("/bbb")
|
||||
o(a.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
|
||||
a.removeAttribute("href")
|
||||
|
||||
o(a.hasAttribute("href")).equals(false)
|
||||
o(a.getAttribute("href")).equals(null)
|
||||
o("href" in a.attributes).equals(false)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -497,6 +548,45 @@ o.spec("domMock", function() {
|
|||
|
||||
o(a.parentNode).equals(null)
|
||||
})
|
||||
o("empty SVG document", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>"
|
||||
|
||||
o(typeof div.firstChild).notEquals(undefined)
|
||||
o(div.firstChild.nodeName).equals("svg")
|
||||
o(div.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(div.firstChild.childNodes.length).equals(0)
|
||||
})
|
||||
o("text elements", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.innerHTML =
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\">"
|
||||
+ "<text>hello</text>"
|
||||
+ "<text> </text>"
|
||||
+ "<text>world</text>"
|
||||
+ "</svg>"
|
||||
|
||||
o(div.firstChild.nodeName).equals("svg")
|
||||
o(div.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
|
||||
var nodes = div.firstChild.childNodes
|
||||
o(nodes.length).equals(3)
|
||||
o(nodes[0].nodeName).equals("text")
|
||||
o(nodes[0].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(nodes[0].childNodes.length).equals(1)
|
||||
o(nodes[0].childNodes[0].nodeName).equals("#text")
|
||||
o(nodes[0].childNodes[0].nodeValue).equals("hello")
|
||||
o(nodes[1].nodeName).equals("text")
|
||||
o(nodes[1].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(nodes[1].childNodes.length).equals(1)
|
||||
o(nodes[1].childNodes[0].nodeName).equals("#text")
|
||||
o(nodes[1].childNodes[0].nodeValue).equals(" ")
|
||||
o(nodes[2].nodeName).equals("text")
|
||||
o(nodes[2].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(nodes[2].childNodes.length).equals(1)
|
||||
o(nodes[2].childNodes[0].nodeName).equals("#text")
|
||||
o(nodes[2].childNodes[0].nodeValue).equals("world")
|
||||
})
|
||||
})
|
||||
o.spec("focus", function() {
|
||||
o("body is active by default", function() {
|
||||
|
|
@ -528,6 +618,7 @@ o.spec("domMock", function() {
|
|||
|
||||
o(div.style.backgroundColor).equals("red")
|
||||
o(div.style.borderBottom).equals("1px solid red")
|
||||
o(div.attributes.style.value).equals("background-color: red; border-bottom: 1px solid red;")
|
||||
})
|
||||
o("removing via setting style.cssText string works", function() {
|
||||
var div = $document.createElement("div")
|
||||
|
|
@ -535,6 +626,7 @@ o.spec("domMock", function() {
|
|||
div.style.cssText = ""
|
||||
|
||||
o(div.style.background).equals("")
|
||||
o(div.attributes.style.value).equals("")
|
||||
})
|
||||
o("the final semicolon is optional when setting style.cssText", function() {
|
||||
var div = $document.createElement("div")
|
||||
|
|
@ -542,6 +634,7 @@ o.spec("domMock", function() {
|
|||
|
||||
o(div.style.background).equals("red")
|
||||
o(div.style.cssText).equals("background: red;")
|
||||
o(div.attributes.style.value).equals("background: red;")
|
||||
})
|
||||
o("'cssText' as a property name is ignored when setting style.cssText", function(){
|
||||
var div = $document.createElement("div")
|
||||
|
|
@ -611,13 +704,57 @@ o.spec("domMock", function() {
|
|||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div)
|
||||
})
|
||||
o("removeEventListener works", function(done) {
|
||||
o("removeEventListener works (bubbling phase)", function() {
|
||||
div.addEventListener("click", spy, false)
|
||||
div.removeEventListener("click", spy, false)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
o("removeEventListener works (capture phase)", function() {
|
||||
div.addEventListener("click", spy, true)
|
||||
div.removeEventListener("click", spy, true)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
o("removeEventListener is selective (bubbling phase)", function() {
|
||||
var other = o.spy()
|
||||
div.addEventListener("click", spy, false)
|
||||
div.addEventListener("click", other, false)
|
||||
div.removeEventListener("click", spy, false)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(other.callCount).equals(1)
|
||||
})
|
||||
o("removeEventListener is selective (capture phase)", function() {
|
||||
var other = o.spy()
|
||||
div.addEventListener("click", spy, true)
|
||||
div.addEventListener("click", other, true)
|
||||
div.removeEventListener("click", spy, true)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(other.callCount).equals(1)
|
||||
})
|
||||
o("removeEventListener only removes the handler related to a given phase (1/2)", function() {
|
||||
spy = o.spy(function(e) {o(e.eventPhase).equals(3)})
|
||||
$document.body.addEventListener("click", spy, true)
|
||||
$document.body.addEventListener("click", spy, false)
|
||||
$document.body.removeEventListener("click", spy, true)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("removeEventListener only removes the handler related to a given phase (2/2)", function() {
|
||||
spy = o.spy(function(e) {o(e.eventPhase).equals(1)})
|
||||
$document.body.addEventListener("click", spy, true)
|
||||
$document.body.addEventListener("click", spy, false)
|
||||
$document.body.removeEventListener("click", spy, false)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("click fires onclick", function() {
|
||||
div.onclick = spy
|
||||
|
|
@ -655,6 +792,488 @@ o.spec("domMock", function() {
|
|||
done()
|
||||
})
|
||||
})
|
||||
o.spec("capture and bubbling phases", function() {
|
||||
var div, e
|
||||
o.beforeEach(function() {
|
||||
div = $document.createElement("div")
|
||||
e = $document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
$document.body.appendChild(div)
|
||||
})
|
||||
o.afterEach(function() {
|
||||
$document.body.removeChild(div)
|
||||
})
|
||||
o("capture and bubbling events both fire on the target in the order they were defined, regardless of the phase", function () {
|
||||
var sequence = []
|
||||
var capture = o.spy(function(ev){
|
||||
sequence.push("capture")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(2)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals(div)
|
||||
})
|
||||
var bubble = o.spy(function(ev){
|
||||
sequence.push("bubble")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(2)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals(div)
|
||||
})
|
||||
|
||||
div.addEventListener("click", bubble, false)
|
||||
div.addEventListener("click", capture, true)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capture.callCount).equals(1)
|
||||
o(bubble.callCount).equals(1)
|
||||
o(sequence).deepEquals(["bubble", "capture"])
|
||||
})
|
||||
o("capture and bubbling events both fire on the parent", function () {
|
||||
var sequence = []
|
||||
var capture = o.spy(function(ev){
|
||||
sequence.push("capture")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(1)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals($document.body)
|
||||
})
|
||||
var bubble = o.spy(function(ev){
|
||||
sequence.push("bubble")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(3)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals($document.body)
|
||||
})
|
||||
|
||||
$document.body.addEventListener("click", bubble, false)
|
||||
$document.body.addEventListener("click", capture, true)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capture.callCount).equals(1)
|
||||
o(bubble.callCount).equals(1)
|
||||
o(sequence).deepEquals(["capture", "bubble"])
|
||||
})
|
||||
o("useCapture defaults to false", function () {
|
||||
var sequence = []
|
||||
var parent = o.spy(function(ev){
|
||||
sequence.push("parent")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(3)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals($document.body)
|
||||
})
|
||||
var target = o.spy(function(ev){
|
||||
sequence.push("target")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(2)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals(div)
|
||||
})
|
||||
|
||||
$document.body.addEventListener("click", parent)
|
||||
div.addEventListener("click", target)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(parent.callCount).equals(1)
|
||||
o(target.callCount).equals(1)
|
||||
o(sequence).deepEquals(["target", "parent"])
|
||||
})
|
||||
o("legacy handlers fire on the bubbling phase", function () {
|
||||
var sequence = []
|
||||
var parent = o.spy(function(ev){
|
||||
sequence.push("parent")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(3)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals($document.body)
|
||||
})
|
||||
var target = o.spy(function(ev){
|
||||
sequence.push("target")
|
||||
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(2)
|
||||
o(ev.target).equals(div)
|
||||
o(ev.currentTarget).equals(div)
|
||||
})
|
||||
|
||||
$document.body.addEventListener("click", parent)
|
||||
$document.body.onclick = parent
|
||||
div.addEventListener("click", target)
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(parent.callCount).equals(2)
|
||||
o(target.callCount).equals(1)
|
||||
o(sequence).deepEquals(["target", "parent", "parent"])
|
||||
})
|
||||
o("events do not propagate to child nodes", function() {
|
||||
var target = o.spy(function(ev){
|
||||
o(ev).equals(e)
|
||||
o(ev.eventPhase).equals(2)
|
||||
o(ev.target).equals($document.body)
|
||||
o(ev.currentTarget).equals($document.body)
|
||||
})
|
||||
var child = o.spy(function(){
|
||||
})
|
||||
|
||||
$document.body.addEventListener("click", target)
|
||||
div.addEventListener("click", child)
|
||||
$document.body.dispatchEvent(e)
|
||||
|
||||
o(target.callCount).equals(1)
|
||||
o(child.callCount).equals(0)
|
||||
})
|
||||
o("e.stopPropagation 1/6", function () {
|
||||
var capParent = o.spy(function(e){e.stopPropagation()})
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(0)
|
||||
o(bubTarget.callCount).equals(0)
|
||||
o(legacyTarget.callCount).equals(0)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
o("e.stopPropagation 2/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy(function(e){e.stopPropagation()})
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("e.stopPropagation 3/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy(function(e){e.stopPropagation()})
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
o("e.stopPropagation 4/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy(function(e){e.stopPropagation()})
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
o("e.stopPropagation 5/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy(function(e){e.stopPropagation()})
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(1)
|
||||
o(legacyParent.callCount).equals(1)
|
||||
})
|
||||
o("e.stopPropagation 6/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy(function(e){e.stopPropagation()})
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(1)
|
||||
o(legacyParent.callCount).equals(1)
|
||||
})
|
||||
o("e.stopImmediatePropagation 1/6", function () {
|
||||
var capParent = o.spy(function(e){e.stopImmediatePropagation()})
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(0)
|
||||
o(bubTarget.callCount).equals(0)
|
||||
o(legacyTarget.callCount).equals(0)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
o("e.stopImmediatePropagation 2/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy(function(e){e.stopImmediatePropagation()})
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(0)
|
||||
o(legacyTarget.callCount).equals(0)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("e.stopImmediatePropagation 3/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy(function(e){e.stopImmediatePropagation()})
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(0)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
o("e.stopImmediatePropagation 4/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy(function(e){e.stopImmediatePropagation()})
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(0)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
o("e.stopImmediatePropagation 5/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubParent = o.spy(function(e){e.stopImmediatePropagation()})
|
||||
var legacyParent = o.spy()
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(1)
|
||||
o(legacyParent.callCount).equals(0)
|
||||
})
|
||||
o("e.stopImmediatePropagation 6/6", function () {
|
||||
var capParent = o.spy()
|
||||
var capTarget = o.spy()
|
||||
var legacyTarget = o.spy()
|
||||
var bubTarget = o.spy()
|
||||
var bubParent = o.spy()
|
||||
var legacyParent = o.spy(function(e){e.stopImmediatePropagation()})
|
||||
|
||||
$document.body.addEventListener("click", capParent, true)
|
||||
$document.body.addEventListener("click", bubParent, false)
|
||||
$document.body.onclick = legacyParent
|
||||
|
||||
div.addEventListener("click", capTarget, true)
|
||||
div.addEventListener("click", bubTarget, false)
|
||||
div.onclick = legacyTarget
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(capParent.callCount).equals(1)
|
||||
o(capTarget.callCount).equals(1)
|
||||
o(bubTarget.callCount).equals(1)
|
||||
o(legacyTarget.callCount).equals(1)
|
||||
o(bubParent.callCount).equals(1)
|
||||
o(legacyParent.callCount).equals(1)
|
||||
})
|
||||
o("errors thrown in handlers don't interrupt the chain", function(done) {
|
||||
var errMsg = "The presence of these six errors in the log is expected in non-Node.js environments"
|
||||
var handler = o.spy(function(){throw errMsg})
|
||||
|
||||
$document.body.addEventListener("click", handler, true)
|
||||
$document.body.addEventListener("click", handler, false)
|
||||
$document.body.onclick = handler
|
||||
|
||||
div.addEventListener("click", handler, true)
|
||||
div.addEventListener("click", handler, false)
|
||||
div.onclick = handler
|
||||
|
||||
div.dispatchEvent(e)
|
||||
|
||||
o(handler.callCount).equals(6)
|
||||
|
||||
// Swallow the async errors in NodeJS
|
||||
if (typeof process !== "undefined" && typeof process.once === "function"){
|
||||
process.once("uncaughtException", function(e) {
|
||||
if (e !== errMsg) throw e
|
||||
process.once("uncaughtException", function(e) {
|
||||
if (e !== errMsg) throw e
|
||||
process.once("uncaughtException", function(e) {
|
||||
if (e !== errMsg) throw e
|
||||
process.once("uncaughtException", function(e) {
|
||||
if (e !== errMsg) throw e
|
||||
process.once("uncaughtException", function(e) {
|
||||
if (e !== errMsg) throw e
|
||||
process.once("uncaughtException", function(e) {
|
||||
if (e !== errMsg) throw e
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("attributes", function() {
|
||||
o.spec("a[href]", function() {
|
||||
|
|
@ -678,6 +1297,13 @@ o.spec("domMock", function() {
|
|||
o(a.href).notEquals("")
|
||||
o(a.attributes["href"].value).equals("")
|
||||
})
|
||||
o("property is read-only for SVG elements", function() {
|
||||
var a = $document.createElementNS("http://www.w3.org/2000/svg", "a")
|
||||
a.href = "/foo"
|
||||
|
||||
o(a.href).deepEquals({baseVal: "", animVal: ""})
|
||||
o("href" in a.attributes).equals(false)
|
||||
})
|
||||
})
|
||||
o.spec("input[checked]", function() {
|
||||
o("only exists in input elements", function() {
|
||||
|
|
@ -731,6 +1357,18 @@ o.spec("domMock", function() {
|
|||
|
||||
o(input.checked).equals(true)
|
||||
})
|
||||
o("doesn't toggle on click when preventDefault() is used", function() {
|
||||
var input = $document.createElement("input")
|
||||
input.setAttribute("type", "checkbox")
|
||||
input.checked = false
|
||||
input.onclick = function(e) {e.preventDefault()}
|
||||
|
||||
var e = $document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
input.dispatchEvent(e)
|
||||
|
||||
o(input.checked).equals(false)
|
||||
})
|
||||
})
|
||||
o.spec("input[value]", function() {
|
||||
o("only exists in input elements", function() {
|
||||
|
|
@ -954,11 +1592,11 @@ o.spec("domMock", function() {
|
|||
o(select.selectedIndex).equals(1)
|
||||
}
|
||||
})
|
||||
o("option.value = null is converted to the empty string", function() {
|
||||
o("option.value = null is converted to 'null'", function() {
|
||||
var option = $document.createElement("option")
|
||||
option.value = null
|
||||
|
||||
o(option.value).equals("")
|
||||
o(option.value).equals("null")
|
||||
})
|
||||
o("setting valid value works with optgroup", function() {
|
||||
var select = $document.createElement("select")
|
||||
|
|
@ -1254,4 +1892,62 @@ o.spec("domMock", function() {
|
|||
o(spies.valueSetter.args[0]).equals("aaa")
|
||||
})
|
||||
})
|
||||
o.spec("DOMParser for SVG", function(){
|
||||
var $DOMParser
|
||||
o.beforeEach(function() {
|
||||
$DOMParser = $window.DOMParser
|
||||
})
|
||||
o("basics", function(){
|
||||
o(typeof $DOMParser).equals("function")
|
||||
|
||||
var parser = new $DOMParser()
|
||||
|
||||
o(parser instanceof $DOMParser).equals(true)
|
||||
o(typeof parser.parseFromString).equals("function")
|
||||
})
|
||||
o("empty document", function() {
|
||||
var parser = new $DOMParser()
|
||||
var doc = parser.parseFromString(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>",
|
||||
"image/svg+xml"
|
||||
)
|
||||
|
||||
o(typeof doc.documentElement).notEquals(undefined)
|
||||
o(doc.documentElement.nodeName).equals("svg")
|
||||
o(doc.documentElement.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(doc.documentElement.childNodes.length).equals(0)
|
||||
})
|
||||
o("text elements", function() {
|
||||
var parser = new $DOMParser()
|
||||
var doc = parser.parseFromString(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\">"
|
||||
+ "<text>hello</text>"
|
||||
+ "<text> </text>"
|
||||
+ "<text>world</text>"
|
||||
+ "</svg>",
|
||||
"image/svg+xml"
|
||||
)
|
||||
|
||||
o(doc.documentElement.nodeName).equals("svg")
|
||||
o(doc.documentElement.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
|
||||
var nodes = doc.documentElement.childNodes
|
||||
o(nodes.length).equals(3)
|
||||
o(nodes[0].nodeName).equals("text")
|
||||
o(nodes[0].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(nodes[0].childNodes.length).equals(1)
|
||||
o(nodes[0].childNodes[0].nodeName).equals("#text")
|
||||
o(nodes[0].childNodes[0].nodeValue).equals("hello")
|
||||
o(nodes[1].nodeName).equals("text")
|
||||
o(nodes[1].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(nodes[1].childNodes.length).equals(1)
|
||||
o(nodes[1].childNodes[0].nodeName).equals("#text")
|
||||
o(nodes[1].childNodes[0].nodeValue).equals(" ")
|
||||
o(nodes[2].nodeName).equals("text")
|
||||
o(nodes[2].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(nodes[2].childNodes.length).equals(1)
|
||||
o(nodes[2].childNodes[0].nodeName).equals("#text")
|
||||
o(nodes[2].childNodes[0].nodeValue).equals("world")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
91
test-utils/tests/test-throttleMock.js
Normal file
91
test-utils/tests/test-throttleMock.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
o.spec("throttleMock", function() {
|
||||
o("works with one callback", function() {
|
||||
var throttleMock = throttleMocker()
|
||||
var spy = o.spy()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
|
||||
var throttled = throttleMock.throttle(spy)
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttled()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttled()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("works with two callbacks", function() {
|
||||
var throttleMock = throttleMocker()
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
|
||||
var throttled1 = throttleMock.throttle(spy1)
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled1()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled1()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
var throttled2 = throttleMock.throttle(spy2)
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled2()
|
||||
|
||||
o(throttleMock.queueLength()).equals(2)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled2()
|
||||
|
||||
o(throttleMock.queueLength()).equals(2)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
27
test-utils/throttleMock.js
Normal file
27
test-utils/throttleMock.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"use strict"
|
||||
|
||||
module.exports = function() {
|
||||
var queue = []
|
||||
return {
|
||||
throttle: function(fn) {
|
||||
var pending = false
|
||||
return function() {
|
||||
if (!pending) {
|
||||
queue.push(function(){
|
||||
pending = false
|
||||
fn()
|
||||
})
|
||||
pending = true
|
||||
}
|
||||
}
|
||||
},
|
||||
fire: function() {
|
||||
var tasks = queue
|
||||
queue = []
|
||||
tasks.forEach(function(fn) {fn()})
|
||||
},
|
||||
queueLength: function(){
|
||||
return queue.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
<script src="../querystring/parse.js"></script>
|
||||
<script src="../test-utils/parseURL.js"></script>
|
||||
<script src="../test-utils/callAsync.js"></script>
|
||||
<script src="../test-utils/components.js"></script>
|
||||
<script src="../test-utils/domMock.js"></script>
|
||||
<script src="../test-utils/pushStateMock.js"></script>
|
||||
<script src="../test-utils/xhrMock.js"></script>
|
||||
|
|
|
|||
|
|
@ -163,14 +163,24 @@ o.spec("api", function() {
|
|||
var count = 0
|
||||
var root = window.document.createElement("div")
|
||||
m.mount(root, createComponent({view: function() {count++}}))
|
||||
o(count).equals(1)
|
||||
m.redraw()
|
||||
o(count).equals(1)
|
||||
setTimeout(function() {
|
||||
m.redraw()
|
||||
|
||||
o(count).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
o("sync", function() {
|
||||
var root = window.document.createElement("div")
|
||||
var view = o.spy()
|
||||
m.mount(root, createComponent({view: view}))
|
||||
o(view.callCount).equals(1)
|
||||
m.redraw.sync()
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue