assert(err instanceof Error, `non-error thrown: ${err}`);

if (404 == err.status || err.expose) return;

keep keep keep keep replace keep keep keep keep

if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));


'use strict';

const assert = require('assert');
const context = require('../helpers/context');

describe('ctx.search=', () => {
  it('should replace the search', () => {
    const ctx = context({ url: '/store/shoes' });
    ctx.search = '?page=2&color=blue';
    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');
    assert.equal(ctx.search, '?page=2&color=blue');
  });

  it('should update ctx.querystring and ctx.query', () => {
    const ctx = context({ url: '/store/shoes' });
    ctx.search = '?page=2&color=blue';
    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');
    assert.equal(ctx.querystring, 'page=2&color=blue');
    assert.equal(ctx.query.page, '2');
    assert.equal(ctx.query.color, 'blue');
  });

  it('should change .url but not .originalUrl', () => {
    const ctx = context({ url: '/store/shoes' });
    ctx.search = '?page=2&color=blue';
    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');
    assert.equal(ctx.originalUrl, '/store/shoes');
    assert.equal(ctx.request.originalUrl, '/store/shoes');
  });

  describe('when missing', () => {
    it('should return ""', () => {
      const ctx = context({ url: '/store/shoes' });
      assert.equal(ctx.search, '');
    });
  });
});


# Response

  A Koa `Response` object is an abstraction on top of node's vanilla response object,
  providing additional functionality that is useful for every day HTTP server
  development.

## API

### response.header

  Response header object.

### response.headers

  Response header object. Alias as `response.header`.

### response.socket

  Request socket.

### response.status

  Get response status. By default, `response.status` is set to `404` unlike node's `res.statusCode` which defaults to `200`.

### response.status=

  Set response status via numeric code:

  - 100 "continue"
  - 101 "switching protocols"
  - 102 "processing"
  - 200 "ok"
  - 201 "created"
  - 202 "accepted"
  - 203 "non-authoritative information"
  - 204 "no content"
  - 205 "reset content"
  - 206 "partial content"
  - 207 "multi-status"
  - 208 "already reported"
  - 226 "im used"
  - 300 "multiple choices"
  - 301 "moved permanently"
  - 302 "found"
  - 303 "see other"
  - 304 "not modified"
  - 305 "use proxy"
  - 307 "temporary redirect"
  - 308 "permanent redirect"
  - 400 "bad request"
  - 401 "unauthorized"
  - 402 "payment required"
  - 403 "forbidden"
  - 404 "not found"
  - 405 "method not allowed"
  - 406 "not acceptable"
  - 407 "proxy authentication required"
  - 408 "request timeout"
  - 409 "conflict"
  - 410 "gone"
  - 411 "length required"
  - 412 "precondition failed"
  - 413 "payload too large"
  - 414 "uri too long"
  - 415 "unsupported media type"
  - 416 "range not satisfiable"
  - 417 "expectation failed"
  - 418 "I'm a teapot"
  - 422 "unprocessable entity"
  - 423 "locked"
  - 424 "failed dependency"
  - 426 "upgrade required"
  - 428 "precondition required"
  - 429 "too many requests"
  - 431 "request header fields too large"
  - 500 "internal server error"
  - 501 "not implemented"
  - 502 "bad gateway"
  - 503 "service unavailable"
  - 504 "gateway timeout"
  - 505 "http version not supported"
  - 506 "variant also negotiates"
  - 507 "insufficient storage"
  - 508 "loop detected"
  - 510 "not extended"
  - 511 "network authentication required"

__NOTE__: don't worry too much about memorizing these strings,
if you have a typo an error will be thrown, displaying this list
so you can make a correction.

### response.message

  Get response status message. By default, `response.message` is
  associated with `response.status`.

### response.message=

  Set response status message to the given value.

### response.length=

  Set response Content-Length to the given value.

### response.length

  Return response Content-Length as a number when present, or deduce
  from `ctx.body` when possible, or `undefined`.

### response.body

  Get response body.

### response.body=

  Set response body to one of the following:

  - `string` written
  - `Buffer` written
  - `Stream` piped
  - `Object` || `Array` json-stringified
  - `null` no content response

If `response.status` has not been set, Koa will automatically set the status to `200` or `204`.

#### String

  The Content-Type is defaulted to text/html or text/plain, both with
  a default charset of utf-8. The Content-Length field is also set.

#### Buffer

  The Content-Type is defaulted to application/octet-stream, and Content-Length
  is also set.

#### Stream

  The Content-Type is defaulted to application/octet-stream.

  Whenever a stream is set as the response body, `.onerror` is automatically added as a listener to the `error` event to catch any errors.
  In addition, whenever the request is closed (even prematurely), the stream is destroyed.
  If you do not want these two features, do not set the stream as the body directly.
  For example, you may not want this when setting the body as an HTTP stream in a proxy as it would destroy the underlying connection.

  See: https://github.com/koajs/koa/pull/612 for more information.

  Here's an example of stream error handling without automatically destroying the stream:

```js
const PassThrough = require('stream').PassThrough;

app.use(async ctx => {
  ctx.body = someHTTPStream.on('error', ctx.onerror).pipe(PassThrough());
});
```

#### Object

  The Content-Type is defaulted to application/json. This includes plain objects `{ foo: 'bar' }` and arrays `['foo', 'bar']`.

### response.get(field)

  Get a response header field value with case-insensitive `field`.

```js
const etag = ctx.response.get('ETag');
```

### response.set(field, value)

  Set response header `field` to `value`:

```js
ctx.set('Cache-Control', 'no-cache');
```

### response.append(field, value)
  Append additional header `field` with value `val`.

```js
ctx.append('Link', '<http://127.0.0.1/>');
```

### response.set(fields)

  Set several response header `fields` with an object:

```js
ctx.set({
  'Etag': '1234',
  'Last-Modified': date
});
```

### response.remove(field)

  Remove header `field`.

### response.type

  Get response `Content-Type` void of parameters such as "charset".

```js
const ct = ctx.type;
// => "image/png"
```

### response.type=

  Set response `Content-Type` via mime string or file extension.

```js
ctx.type = 'text/plain; charset=utf-8';
ctx.type = 'image/png';
ctx.type = '.png';
ctx.type = 'png';
```

  Note: when appropriate a `charset` is selected for you, for
  example `response.type = 'html'` will default to "utf-8". If you need to overwrite `charset`,
  use `ctx.set('Content-Type', 'text/html')` to set response header field to value directly.

### response.is(types...)

  Very similar to `ctx.request.is()`.
  Check whether the response type is one of the supplied types.
  This is particularly useful for creating middleware that
  manipulate responses.

  For example, this is a middleware that minifies
  all HTML responses except for streams.

```js
const minify = require('html-minifier');

app.use(async (ctx, next) => {
  await next();

  if (!ctx.response.is('html')) return;

  let body = ctx.body;
  if (!body || body.pipe) return;

  if (Buffer.isBuffer(body)) body = body.toString();
  ctx.body = minify(body);
});
```

### response.redirect(url, [alt])

  Perform a [302] redirect to `url`.

  The string "back" is special-cased
  to provide Referrer support, when Referrer
  is not present `alt` or "/" is used.

```js
ctx.redirect('back');
ctx.redirect('back', '/index.html');
ctx.redirect('/login');
ctx.redirect('http://google.com');
```

  To alter the default status of `302`, simply assign the status
  before or after this call. To alter the body, assign it after this call:

```js
ctx.status = 301;
ctx.redirect('/cart');
ctx.body = 'Redirecting to shopping cart';
```

### response.attachment([filename])

  Set `Content-Disposition` to "attachment" to signal the client
  to prompt for download. Optionally specify the `filename` of the
  download.

### response.headerSent

  Check if a response header has already been sent. Useful for seeing
  if the client may be notified on error.

### response.lastModified

  Return the `Last-Modified` header as a `Date`, if it exists.

### response.lastModified=

  Set the `Last-Modified` header as an appropriate UTC string.
  You can either set it as a `Date` or date string.

```js
ctx.response.lastModified = new Date();
```

### response.etag=

  Set the ETag of a response including the wrapped `"`s.
  Note that there is no corresponding `response.etag` getter.

```js
ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');
```

### response.vary(field)

  Vary on `field`.

### response.flushHeaders()

  Flush any set headers, and begin the body.


'use strict';

const assert = require('assert');
const request = require('../helpers/context').request;

describe('req.subdomains', () => {
  it('should return subdomain array', () => {
    const req = request();
    req.header.host = 'tobi.ferrets.example.com';
    req.app.subdomainOffset = 2;
    assert.deepEqual(req.subdomains, ['ferrets', 'tobi']);

    req.app.subdomainOffset = 3;
    assert.deepEqual(req.subdomains, ['tobi']);
  });

  it('should work with no host present', () => {
    const req = request();
    assert.deepEqual(req.subdomains, []);
  });

  it('should check if the host is an ip address, even with a port', () => {
    const req = request();
    req.header.host = '127.0.0.1:3000';
    assert.deepEqual(req.subdomains, []);
  });
});


keep keep keep replace keep keep keep keep keep

'use strict';

const assert = require('assert');
const Koa = require('../..');
const AssertionError = require('assert').AssertionError;

describe('app.onerror(err)', () => {
  beforeEach(() => {
    global.console = jest.genMockFromModule('console');
  });

  afterEach(() => {
    global.console = require('console');
  });

  it('should throw an error if a non-error is given', () => {
    const app = new Koa();

    assert.throws(() => {
      app.onerror('foo');
    }, AssertionError, 'non-error thrown: foo');
  });

  it('should do nothing if status is 404', () => {
    const app = new Koa();
    const err = new Error();

    err.status = 404;

    app.onerror(err);

    assert.deepEqual(console.error.mock.calls, []);
  });

  it('should do nothing if .silent', () => {
    const app = new Koa();
    app.silent = true;
    const err = new Error();

    app.onerror(err);

    assert.deepEqual(console.error.mock.calls, []);
  });

  it('should log the error to stderr', () => {
    const app = new Koa();
    app.env = 'dev';

    const err = new Error();
    err.stack = 'Foo';

    app.onerror(err);

    const stderr = console.error.mock.calls.join('\n');
    assert.deepEqual(stderr, '\n  Foo\n');
  });

  it('should use err.toString() instad of err.stack', () => {
    const app = new Koa();
    app.env = 'dev';

    const err = new Error('mock stack null');
    err.stack = null;

    app.onerror(err);

    const stderr = console.error.mock.calls.join('\n');
    assert.equal(stderr, '\n  Error: mock stack null\n');
  });
});


'use strict';

const assert = require('assert');
const context = require('../helpers/context');

describe('ctx.acceptsCharsets()', () => {
  describe('with no arguments', () => {
    describe('when Accept-Charset is populated', () => {
      it('should return accepted types', () => {
        const ctx = context();
        ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
        assert.deepEqual(ctx.acceptsCharsets(), ['utf-8', 'utf-7', 'iso-8859-1']);
      });
    });
  });

  describe('with multiple arguments', () => {
    describe('when Accept-Charset is populated', () => {
      describe('if any types match', () => {
        it('should return the best fit', () => {
          const ctx = context();
          ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
          assert.equal(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-8');
        });
      });

      describe('if no types match', () => {
        it('should return false', () => {
          const ctx = context();
          ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
          assert.equal(ctx.acceptsCharsets('utf-16'), false);
        });
      });
    });

    describe('when Accept-Charset is not populated', () => {
      it('should return the first type', () => {
        const ctx = context();
        assert.equal(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-7');
      });
    });
  });

  describe('with an array', () => {
    it('should return the best fit', () => {
      const ctx = context();
      ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
      assert.equal(ctx.acceptsCharsets(['utf-7', 'utf-8']), 'utf-8');
    });
  });
});


'use strict';

const assert = require('assert');
const context = require('../helpers/context');

describe('ctx.acceptsLanguages(langs)', () => {
  describe('with no arguments', () => {
    describe('when Accept-Language is populated', () => {
      it('should return accepted types', () => {
        const ctx = context();
        ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
        assert.deepEqual(ctx.acceptsLanguages(), ['es', 'pt', 'en']);
      });
    });
  });

  describe('with multiple arguments', () => {
    describe('when Accept-Language is populated', () => {
      describe('if any types types match', () => {
        it('should return the best fit', () => {
          const ctx = context();
          ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
          assert.equal(ctx.acceptsLanguages('es', 'en'), 'es');
        });
      });

      describe('if no types match', () => {
        it('should return false', () => {
          const ctx = context();
          ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
          assert.equal(ctx.acceptsLanguages('fr', 'au'), false);
        });
      });
    });

    describe('when Accept-Language is not populated', () => {
      it('should return the first type', () => {
        const ctx = context();
        assert.equal(ctx.acceptsLanguages('es', 'en'), 'es');
      });
    });
  });

  describe('with an array', () => {
    it('should return the best fit', () => {
      const ctx = context();
      ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
      assert.equal(ctx.acceptsLanguages(['es', 'en']), 'es');
    });
  });
});


'use strict';

const assert = require('assert');
const context = require('../helpers/context');

describe('ctx.remove(name)', () => {
  it('should remove a field', () => {
    const ctx = context();
    ctx.set('x-foo', 'bar');
    ctx.remove('x-foo');
    assert.deepEqual(ctx.response.header, {});
  });
});


}, AssertionError, 'non-error thrown: foo');

it('should do nothing if status is 404', () => {


'use strict';

const request = require('../helpers/context').request;
const assert = require('assert');

describe('ctx.length', () => {
  it('should return length in content-length', () => {
    const req = request();
    req.header['content-length'] = '10';
    assert.equal(req.length, 10);
  });

  it('with no content-length present', () => {
    const req = request();
    assert.equal(req.length, undefined);
  });
});


Component.prototype.forceUpdate = function(callback) {

diff(this.base, this.base.parentNode, this._vnode, this._vnode, this.context, false, true, null, 1, []);

keep keep keep keep replace keep keep keep keep keep

diff(this.base, this.base.parentNode, this._vnode, this._vnode, this.context, false, true, null, true, []);

import { EMPTY_OBJ, EMPTY_ARR } from '../constants';
// import { assign } from '../util';
import { Component /* , enqueueRender */ } from '../component';
import { coerceToVNode /*, reclaimVNode*/ } from '../create-element';
import { diffChildren /*, create */ } from './children';
import { diffProps } from './props';
import { assign } from '../util';
// import { processQueue } from '../component';

// let hash = [
// 	['type', ['number']],
// 	['tag', [null,'string','function']],
// 	['props', ['object']],
// 	['children', ['object']],
// 	['text', ['string', null]],
// 	['key', [null,'string']],
// 	['index', [null,'number']],
// 	['_children', ['object']],
// 	['_el', ['object']],
// 	['_component', ['object']]
// ];

// function assertIsPureVNode(obj) {
// 	let index = 0;
// 	for (let i in obj) {
// 		let p = hash[index];
// 		if (i!==p[0]) return console.error('VNode property error ('+i+' !== '+p[0]+')');
// 		let isMatch = false;
// 		for (let j=0; j<p[1].length; j++) {
// 			if (obj[i]==p[1][j] || typeof obj[i]===p[1][j]) {
// 				isMatch = true;
// 				break;
// 			}
// 		}
// 		if (!isMatch) return console.error('VNode type error ('+i+' !~ '+p[1]+'): ', obj[i]);
// 		index++;
// 	}
// }

// const IDLE_OPTIONS = { timeout: 100 };
// let idleQueue = [];
// // let queuePopulated = false;
// // let queueIndex = 0;
// function processIdleQueue() {
// 	let p;
// 	// let processed = 0;
// 	let time = Date.now();
// 	while (Date.now()-time<30 && idleQueue.length!==0) {
// 		if ((p = idleQueue.pop())) {
// 			// eslint-disable-next-line prefer-spread
// 			diff.apply(null, p);
// 			// processed++;
// 		}
// 	}

// 	// if (processed) console.log(processed);

// 	if (idleQueue.length) {
// 		requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
// 	}

// 	// if (queueIndex>=idleQueue.length-1) {
// 	// 	queuePopulated = false;
// 	// 	queueIndex = idleQueue.length = 0;
// 	// }
// 	// else {
// 	// 	requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
// 	// }

// 	// // let len = idleQueue.length;
// 	// // console.log('emptying queue ('+queueIndex+' - '+idleQueue.length+')');
// 	// for ( ; queueIndex<idleQueue.length && !c.didTimeout; queueIndex++) {
// 	// 	// eslint-disable-next-line prefer-spread
// 	// 	diff.apply(null, idleQueue[queueIndex]);
// 	// }
// 	// if (queueIndex>=idleQueue.length-1) {
// 	// 	queuePopulated = false;
// 	// 	queueIndex = idleQueue.length = 0;
// 	// }
// 	// else {
// 	// 	requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
// 	// }
// }

export function diff(dom, parent, newTree, oldTree, context, isSvg, append, excessChildren, isRootDiff, mounts) {
	if (newTree==null) {
		if (oldTree!=null) {
			unmount(oldTree);
		}
		return null;
	}

	// if (dom!=null && now!==true) {
	// 	// console.log('queueing');

	// 	for (let i=0; i<idleQueue.length; i++) {
	// 		if (idleQueue[i]!=null && idleQueue[i][0]===dom) {
	// 			idleQueue[i] = null;
	// 			break;
	// 		}
	// 	}
	// 	if (idleQueue.push([dom, parent, newTree, oldTree, context, isSvg, true])===1) {
	// 		requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
	// 	}

	// 	// let entry = [dom, parent, newTree, oldTree, context, isSvg, true];
	// 	// for (let i=queueIndex; i<idleQueue.length; i++) {
	// 	// 	if (idleQueue[i][0]===dom) {
	// 	// 		console.log('found earlier entry at '+i+' instead of '+idleQueue.length, dom);
	// 	// 		idleQueue[i] = entry;
	// 	// 		entry = null;
	// 	// 		break;
	// 	// 	}
	// 	// }
	// 	// if (entry!=null) idleQueue.push(entry);
	// 	// if (queuePopulated===false) {
	// 	// 	queuePopulated = true;
	// 	// 	requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
	// 	// }

	// 	// setTimeout(processIdleQueue);
	// 	// requestIdleCallback( () => {
	// 	// 	let out = diff(dom, parent, newTree, oldTree, context, isSvg, true);
	// 	// 	if (out!==dom) {
	// 	// 		console.log('replacing ', dom, ' with ', out);
	// 	// 	}
	// 	// 	// if (out==null) dom.remove();
	// 	// 	// else dom.parentNode.replaceChild(out, dom);
	// 	// }, IDLE_OPTIONS);
	// 	return newTree._el = dom;
	// 	// return newTree._el = dom;
	// }

	let originalOldTree = oldTree;

	// if (newTree==null) {
	// 	newTree = createVNode(3, null, null, null, '', null);
	// 	// return;
	// 	// let c = document.createComment('empty');
	// 	// if (parent!=null) {
	// 	// 	if (dom!=null) parent.replaceChild(c, dom);
	// 	// 	else parent.appendChild(c);
	// 	// }
	// 	// if (oldTree!=null) unmount(oldTree);
	// 	// return c;
	// }

	// assertIsPureVNode(newTree);
	// console.log(oldTree, newTree);

	let c, p, isNew = false, oldProps, oldState, oldContext,
		newTag = newTree.tag,
		oldTag = oldTree!=null ? oldTree.tag : null;

	// root of a diff:
	if (isRootDiff) {
		isSvg = parent!=null && parent.ownerSVGElement!==undefined;
	}

	// @TODO unmounting here removes the dom pointer
	// if (newTree.tag!==oldTag) {
	// 	// console.log('mismatched tags: ', oldTag, newTree.tag);
	// 	if (typeof oldTag==='function') {
	// 		unmount(oldTree);
	// 	}
	// 	if (typeof newTree.tag==='function') {
	// 		mount(newTree);
	// 	}
	// }

	outer: if (typeof newTag==='function') {
		if (typeof oldTag==='function' && oldTag!==newTag) {
			// unmount(oldTree);
			oldTree = null;
			// oldTree._component.componentWillUnmount();
			// oldTree._component.base = null;
			// if (oldTree.props.ref!=null) {
			// 	oldTree.props.ref(null);
			// }
		}

		// Get component and set it to `c`
		if (oldTree!=null && oldTree._component) {
			c = newTree._component = oldTree._component;
		}
		else {
			isNew = true;
			// c = newTree._component = new newTree.tag(newTree.props, context);
			c = newTree._component = createComponent(newTree.tag, newTree.props, context);
			c.props = newTree.props;
			if (!c.state) c.state = {};
			c.context = context;
		}

		// Invoke getDerivedStateFromProps
		let s = c._nextState || c.state;
		if (newTag.getDerivedStateFromProps!=null) {
			oldState = assign({}, c.state);
			assign(s, newTag.getDerivedStateFromProps(newTree.props, s));
		}

		// Invoke pre-render lifecycle methods
		if (isNew) {
			if (newTag.getDerivedStateFromProps==null && c.componentWillMount!=null) c.componentWillMount();
			mounts.push(c);

			// if (dom==null && newTree.tag.recycle===true && newTree.tag.__cache!=null) {
			// 	console.log('using recycled DOM');
			// 	c._previousVTree = newTree.tag.__cache.pop();
			// 	c.base = c._previousVTree._el;
			// }

			// if (dom == null && newTree.tag.$cache!=null) {
			// 	dom = newTree.tag.$cache.pop();
			// }
			// if (dom==null && newTree.tag.$precache!=null) {
			// 	if (newTree.tag.$cache==null) {
			// 		let t = document.createElement('template');
			// 		newTree.tag.$cache = newTree.tag.$precache.cloneNode(true);
			// 		t.appendChild(newTree.tag.$cache);
			// 	}
			// 	dom = newTree.tag.$cache.cloneNode(true);
			// }
		}
		else {
			// let nextState = c.state;
			// if (c.prevState!=null) {
			// 	c.state = c.prevState;
			// 	c.prevState = null;
			// }

			// console.log('updating component in-place', c._nextState);
			// if (c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newTree.props, c.state)===false) {
			// 	c.state = nextState;
			if (c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newTree.props, s, context)===false) {
				dom = newTree._el = c.base;
				break outer;
				// return newTree._el = c.base;
			}
			if (newTag.getDerivedStateFromProps==null && c.componentWillReceiveProps!=null) {
				c.componentWillReceiveProps(newTree.props, context);
			}

			if (c.componentWillUpdate!=null) {
				c.componentWillUpdate(newTree.props, s, context);
			}
			// c.state = nextState;
		}

		oldProps = c.props;
		if (!oldState) oldState = c.state;

		oldContext = c.context = context;
		c.props = newTree.props;
		if (c._nextState!=null) {
			c.state = c._nextState;
			c._nextState = null;
		}
		let prev = c._previousVTree;
		let vnode = c._previousVTree = coerceToVNode(c.render(c.props, c.state, c.context));

		if (c.getChildContext!=null) {
			context = assign(assign({}, context), c.getChildContext());
		}

		if (!isNew && c.getSnapshotBeforeUpdate!=null) {
			oldContext = c.getSnapshotBeforeUpdate(oldProps, oldState);
		}

		if (vnode instanceof Array) {
			diffChildren(parent, vnode, prev, EMPTY_OBJ, isSvg, excessChildren, 0, mounts);
		}
		else {
			c.base = diff(dom, parent, vnode, prev, context, isSvg, append, excessChildren, 0, mounts);
		}
		// context = assign({}, context);
		// context.__depth = (context.__depth || 0) + 1;
		// context = assign({
		// 	__depth: (context.__depth || 0) + 1
		// }, context);
		// if (c.getChildContext!=null) {
		// 	assign(context, c.getChildContext());
		// }
		// if (c.id==20) {
		// 	// console.trace('diffing '+c.id);
		// 	console.log('diffing '+c.id, vnode, prev);
		// }
		c._dirty = false;
		// newTree.tag.$precache = c.base;
		c._vnode = newTree;

		// if (dom!=null && c.base!=null && c.base!==dom) {
		// 	parent.replaceChild(c.base, dom);
		// }

		if (c.base==null) {
			if (prev) unmount(prev);
		}
		else if (parent && append!==false) {
			// if (insertBefore && c.base.nextSibling!==insertBefore) parent.insertBefore(c.base, insertBefore);
			if (dom==null || dom.parentNode!==parent) parent.appendChild(c.base);
			else if (c.base!==dom) {
				// console.log('replace', dom, c.base);
				parent.replaceChild(c.base, dom);
			}
		}

		// if (dom!=null && (c.base!==dom || !dom.parentNode)) {
		// 	if (c.base==null) unmount(prev);
		// 	else if (dom.parentNode!==parent) parent.appendChild(c.base);
		// 	else parent.replaceChild(c.base, dom);
		// }

		dom = newTree._el = c.base;
		while (p=c._renderCallbacks.pop()) p();

		// if (c!=null) {
		// c.base = newTree._el;
		if (!isNew && c.componentDidUpdate!=null) {
			c.componentDidUpdate(oldProps, oldState, oldContext);
		}
		// if (isNew) {
		// 	mounts.push(c);
		// 	// if (c.componentDidMount!=null) c.componentDidMount();
		// }
		// else if (c.componentDidUpdate!=null) {
		// 	c.componentDidUpdate(oldProps, oldState, oldContext);
		// }
		// }
	}
	else {
		dom = newTree._el = diffElementNodes(dom, parent, newTree, oldTree, context, isSvg, excessChildren, 0, mounts);
	}

	if (isRootDiff) {
		// processQueue();
		// console.log('firing '+mounts.length+' mounts');
		while ((c = mounts.pop())) {
			if (c.componentDidMount!=null) c.componentDidMount();
		}
	}

	// console.log(isRootDiff);

	// if (oldTree!=null && oldTree!==newTree) unmount(oldTree, true, oldTree._el!==dom);

	// if (originalOldTree && originalOldTree._el && originalOldTree._el!==dom) {
	// 	unmount(originalOldTree);
	// }
	if (originalOldTree!=null && originalOldTree.tag!==newTag) {
		// console.trace('unmount', originalOldTree._el);
		unmount(originalOldTree);
	}

	return dom;
}

function diffElementNodes(dom, parent, vnode, oldVNode, context, isSvg, excessChildren, isRootDiff, mounts) {
	// if (vnode==null) {
	// 	let c = document.createComment('empty');
	// 	if (parent!=null) {
	// 		if (dom!=null) parent.replaceChild(c, dom);
	// 		else parent.appendChild(c);
	// 	}
	// 	if (oldVNode!=null) unmount(oldVNode);
	// 	return c;
	// }

	let d = dom;

	// Tracks entering and exiting SVG namespace when descending through the tree.
	isSvg = vnode.tag === 'svg' ? true : vnode.tag === 'foreignObject' ? false : isSvg;

	// if (oldVNode!=null) {
	// 	if (vnode.type!==oldVNode.type) console.log('vnode type mismatch: ', oldVNode.type, vnode.type);
	// 	if (vnode.tag!==oldVNode.tag) console.log('vnode tag mismatch: ', oldVNode.tag, vnode.tag);
	// }

	if (oldVNode==null || vnode.type!==oldVNode.type || vnode.tag!==oldVNode.tag) {
		// if (oldVNode) unmount(oldVNode);
		dom = null;
	}

	// if (dom==null && excessChildren!=null) {
	// 	console.log('hydrating from '+excessChildren.length+' excess children', excessChildren.map( c => c && `${c.nodeType} @ ${c.localName}`), vnode.type, vnode.tag);
	// 	for (let j=0; j<excessChildren.length; j++) {
	// 		let c = excessChildren[j];
	// 		if (c!=null && c.nodeType==vnode.type && c.localName==vnode.tag) {
	// 			// oldVNode = toVNode(dom = d = c);
	// 			// console.log(vnode, oldVNode, dom);
	// 			excessChildren[j] = null;
	// 			// console.log('found hydration match for '+vnode.tag+' ('+vnode.type+')', oldVNode);
	// 			// console.log(oldVNode, vnode);
	// 			// dom = toVNode(c);
	// 			break;
	// 		}
	// 	}
	// }

	if (dom==null) {
	// if (dom==null || vnode.type!==(oldVNode==null?null:oldVNode.type) || vnode.tag!==(oldVNode==null?null:oldVNode.tag)) {
		// return create(dom, parent, vnode, context, isSvg);
		//dom = create(dom, parent, vnode, context, isSvg);

		// if (vnode.type===3) {
		// 	dom = document.createTextNode(vnode.text);
		// }
		// else if (isSvg) {
		// 	dom = document.createElementNS('http://www.w3.org/2000/svg', vnode.tag);
		// }
		// else {
		// 	dom = document.createElement(vnode.tag);
		// }
		// // else {
		// // 	dom = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', vnode.tag) : document.createElement(vnode.tag);
		// // }

		// vnode._el = dom;

		// vnode._el = dom = typeof vnode==='string' || typeof vnode==='number' ? document.createTextNode(vnode) : isSvg ? document.createElementNS('http://www.w3.org/2000/svg', vnode.tag) : document.createElement(vnode.tag);
		vnode._el = dom = vnode.type===3 ? document.createTextNode(vnode.text) : isSvg ? document.createElementNS('http://www.w3.org/2000/svg', vnode.tag) : document.createElement(vnode.tag);

		// dom = vnode.type===3 ? document.createTextNode(vnode.text) : document.createElement(vnode.tag);
		// if (d) {
		// 	parent.replaceChild(dom, d);
		// 	while (d.firstChild) dom.appendChild(d.firstChild);
		// }
	}

	// if (typeof vnode==='string' || typeof vnode==='number') {
	if (vnode.type===3) {
		if (dom===d && vnode.text!==oldVNode.text) {
			dom.data = vnode.text;
		}
	}
	else {
		if (excessChildren!=null && dom.childNodes!=null) {
			excessChildren = EMPTY_ARR.slice.call(dom.childNodes);
		}
		// console.log('diffChildren(', getVNodeChildren(vnode).map( p => Object.assign({}, p) ), getVNodeChildren(oldVNode).map( p => Object.assign({}, p) ), ')');
		// let newChildren = getVNodeChildren(vnode);
		// diffChildren(dom, newChildren, vnode===oldVNode ? newChildren : oldVNode==null ? [] : getVNodeChildren(oldVNode), context, isSvg, excessChildren);
		diffChildren(dom, getVNodeChildren(vnode), oldVNode==null ? EMPTY_ARR : getVNodeChildren(oldVNode), context, isSvg, excessChildren, isRootDiff, mounts);
		if (vnode!==oldVNode) {
			diffProps(dom, vnode.props, oldVNode==null ? EMPTY_OBJ : oldVNode.props, isSvg);
		}
	}

	// if (oldVNode!=null && dom!==d) unmount(oldVNode);

	return dom;
}

export function unmount(vnode) {
	let r;
	if (vnode.props!=null && (r = vnode.props.ref)) r(null);
	if ((r = vnode._el)!=null) r.remove();
	vnode._el = null;

	if ((r = vnode._component)!=null) {
		if (r.componentWillUnmount) r.componentWillUnmount();
		// let ctor = r.constructor;
		// let cache = ctor.$cache || (ctor.$cache = []);
		// cache.push(r.base);

		// let ctor = r.constructor;
		// if (r.base!=null && ctor.recycle===true) {
		// 	(ctor.__cache || (ctor.__cache=[])).push(r._previousVTree);
		// 	return;
		// }

		r.base = null;
		if (r = r._previousVTree) unmount(r);
	}
	else if (r = vnode._children) {
		for (let i = 0; i < r.length; i++) {
			unmount(r[i]);
		}
	}

	// if (recursive) {
	// 	let children = getVNodeChildren(vnode);
	// 	for (let i=children.length; i--; ) {
	// 		unmount(children[i], true, false);
	// 	}
	// }
}

// export function mount(vnode) {
// 	let r;
// 	if (vnode.props!=null && (r = vnode.props.ref)) r(vnode._component!=null ? vnode._component : vnode._el);
// 	if ((r = vnode._component)) {
// 		if (r.componentWillMount) r.componentWillMount();
// 		r.base = vnode._el;
// 	}
// }

/*
function getVNodeChildren(vnode) {
	if (vnode._children) return vnode._children;
	let list = [],
		stack = vnode.children.slice(),
		key = 0,
		child,
		type;
	while (stack.length) {
		child = stack.pop();
		if (child && child.pop!==undefined) {
			while (child.length) stack.push(child.pop());
		}
		else {
			type = typeof vnode;
			if (type!=='object' && type!=='function') {
				child = createVNode(TEXT_NODE, null, null, null, child, key);
			}
			else if (!child.key) {
				child.key = key;
			}
			list.push(child);
			key++;
		}
	}
	return vnode._children = list;
}
*/

// const ARR = [];

export function getVNodeChildren(vnode) {
	if (vnode._children==null) {
		// flattenChildren(vnode.children, vnode._children=[], '', 0);
		// flattenChildren(vnode.props.children, vnode._children=[], '', 0);
		flattenChildren(vnode.props && vnode.props.children, vnode._children=[]);
	}
	return vnode._children;

	// ARR[0] = vnode.children;
	// flattenChildren(ARR, children, '', 0);
	// flattenChildren(Array.isArray(vnode.children) ? vnode.children : [vnode.children], children, '', 0);
	// if (vnode._children!=null) {
	// 	return vnode._children;
	// }
	// // ARR[0] = vnode.children;
	// // flattenChildren(ARR, children, '', 0);
	// // flattenChildren(Array.isArray(vnode.children) ? vnode.children : [vnode.children], children, '', 0);
	// let children = vnode._children = [];
	// flattenChildren(vnode.children, children, '', 0);
	// return children;
}

// function flattenChildren(children, flattened, path, index) {
// function flattenChildren(children, flattened, key, index) {
function flattenChildren(children, flattened) {
	// let i = 0;
	//	key = path+'.'+index,	//key = path.length===0 ? index : `${path}.${index}`,
	//	isObject = children===Object(children);
	//	child,
	//	type = typeof children;

	// if (key) key += '.';

	// key += '.';
	// key += index;

	// if (type==='object' && typeof Symbol!=='undefined' && Symbol.iterator in children) {
	// 	children = Array.from(children);
	// }
	// if (children==null) {}
	if (children==null || typeof children === 'boolean') {}
	// else if (isObject && 'pop' in children) {
	else if (typeof children==='object' && ('pop' in children || Symbol.iterator in children && (children = Array.from(children)))) {
	// else if (type==='object' && 'pop' in children) {
		for (let i=0; i < children.length; i++) {
			// let child = children[i];
			// let key = path + '.' + i;
			// if ('pop' in child) {
			// 	flattenChildren(child, flattened, key, i);
			// }
			// else {
			// 	pushChild(child, flattened, key, i, typeof child);
			// }
			flattenChildren(children[i], flattened);
			// flattenChildren(children[i], flattened, key, i);
		}
	}
	// else if (type==='object' && Symbol.iterator in children) {
	// 	flattenChildren(Array.from(children), flattened, key, index);
	// }
	// else if (type==='object' && Symbol.iterator in children) {
	// 	let child;
	// 	while ((child = children.next()).done !== true) {
	// 		flattenChildren(child.value, flattened, key, i++);
	// 	}
	// }
	else {
		// pushChild(children, flattened, path, index, type);
		// if (!isObject) {

		// if (typeof children!=='object' && typeof children!=='function') {
		// 	children = createVNode(TEXT_NODE, null, null, null, children, null);
		// 	// children = createVNode(TEXT_NODE, null, null, null, children, key);
		// }
		children = coerceToVNode(children);

		// else if (children.key == null) {
		// 	children.key = key;
		// }
		// if (flattened.indexOf(children)!==-1) {
		// 	children = assign({}, children);
		// }
		flattened.push(children);
	}
}

// function pushChild(child, flattened, path, index, key, type) {
// 	if (type!=='object' && type!=='function') {
// 		child = createVNode(TEXT_NODE, null, null, null, child, key);
// 	}
// 	else if (child.key == null) {
// 		child.key = key;
// 	}
// 	if (flattened.indexOf(child)!==-1) {
// 		child = assign({}, child);
// 	}
// 	flattened.push(child);
// }

// function flattenChildren(children) {
// 	let stack = [children],
// 		result = [],
// 		child;

// 	while (stack.length) {
// 		let item = stack.pop();
// 		if (item && 'pop' in item) {
// 			for (let i=0; i<item.length; i++) stack.push(item[i]);
// 		}
// 		else if (item && Symbol.iterator in item) {
// 			while ((child = item.next()).done!==true) {
// 				stack.push(child.value);
// 			}
// 		}
// 		else {
// 			result.push(item);
// 		}
// 	}

// 	return result.reverse();
// }

function createComponent(Ctor, props, context) {
	let inst;
	if (Ctor.prototype && Ctor.prototype.render) {
		inst = new Ctor(props, context);
		// @TODO this really shouldn't be necessary and people shouldn't rely on it!
		// Component.call(inst, props, context);
	}
	else {
		inst = new Component(props, context);
		inst._constructor = Ctor;
		inst.render = doRender;
	}
	return inst;
}

/** The `.render()` method for a PFC backing instance. */
function doRender(props, state, context) {
	return this._constructor(props, context);
}


export { render, hydrate } from './render';
export { createElement, Fragment } from './create-element';
export { Component } from './component';
export { cloneElement } from './clone-element';

/*
function createElement(nodeName, props) {
	let i, c, el = document.createElement(nodeName);
	for (i in props)
		if (i === 'style') {
			Object.assign(el.style, props[i]);
		}
		else {
			el[i] = props[i];
		}
	for (i = 2; i < arguments.length; i++)
		if ((c = arguments[i]) != null)
			el.appendChild('appendChild' in Object(c) ? c : document.createTextNode(c));
	return el;
}

document.body.appendChild(

HELLO WORLD

hola there

);
*/


node_modules
npm-debug.log
package-lock.json
dist
.vscode


// function createRoot(title) {
// 	let div = document.createElement('div');
// 	let h2 = document.createElement('h2');
// 	h2.textContent = title;
// 	div.appendChild(h2);
// 	document.body.appendChild(div);
// 	return div;
// }

/*
function logCall(obj, method, name) {
	let old = obj[method];
	obj[method] = function(...args) {
		console.log(`<${this.localName}>.`+(name || `${method}(${args})`));
		return old.apply(this, args);
	};
}

logCall(HTMLElement.prototype, 'appendChild');
logCall(HTMLElement.prototype, 'removeChild');
logCall(HTMLElement.prototype, 'insertBefore');
logCall(HTMLElement.prototype, 'replaceChild');
logCall(HTMLElement.prototype, 'setAttribute');
logCall(HTMLElement.prototype, 'removeAttribute');
let d = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');
Object.defineProperty(Text.prototype, 'nodeValue', {
	get() {
		let value = d.get.call(this);
		console.log('get Text#nodeValue: ', value);
		return value;
	},
	set(v) {
		console.log('set Text#nodeValue', v);
		return d.set.call(this, v);
	}
});

render((

This is a test.

		<Foo />
		<time>...</time>

), createRoot('Stateful component update demo:'));

class Foo extends Component {
	componentDidMount() {
		console.log('mounted');
		this.timer = setInterval( () => {
			this.setState({ time: Date.now() });
		}, 5000);
	}
	componentWillUnmount() {
		clearInterval(this.timer);
	}
	render(props, state, context) {
		// console.log('rendering', props, state, context);
		return <time>test: {state.time}</time>
	}
}

render((

This is a test.

		<Foo />
		<time>...</time>

), createRoot('Stateful component update demo:'));

let items = [];
let count = 0;
let three = createRoot('Top-level render demo:');

setInterval( () => {
	if (count++ %20 < 10 ) {
		items.push(
item #{items.length}
);
	}
	else {
		items.shift();
	}

	render((

This is a test.

			<time>{Date.now()}</time>

{items}

	), three);
}, 5000);

// Mount the top-level component to the DOM:
render(, document.body);
*/


if (!isNew && c.getSnapshotBeforeUpdate!=null) {

oldContext = c.getSnapshotBeforeUpdate(oldProps, oldState);

diffChildren(parent, vnode, prev, EMPTY_OBJ, isSvg, excessChildren, 0, mounts);

keep keep keep keep keep replace keep keep

diffChildren(parent, vnode, prev, EMPTY_OBJ, isSvg, excessChildren, false, mounts);

import { assign } from './util';
import { diff } from './diff/index';
// import { diff, diffLevel } from './diff/index';

export function Component(props, context) {
	this.props = props;
	this.context = context;
	// if (this.state==null) this.state = {};
	this.state = {};
	this._dirty = true;
	this._renderCallbacks = [];
}

Component.prototype.setState = function(update, callback) {
	// if (this.prevState==null) this.prevState = assign({}, this.state);
	// let s = this._nextState || this.state;
	// let s = this.state;
	// assign(this.state, update);

	// only clone state when copying to nextState the first time.
	let s = this._nextState || (this._nextState = assign({}, this.state));

	// if update() mutates state in-place, skip the copy:
	if (typeof update!=='function' || (update = update(s, this.props))) {
		assign(s, update);
	}

	// let s = this._nextState || this.state;
	// this._nextState = assign(assign({}, s), typeof update==='function' && update(s, this.props) || s);

	// console.log(update, this._nextState);
	// this._nextState = Object.assign({}, s, update);
	if (callback!=null) this._renderCallbacks.push(callback);

	enqueueRender(this);
	// if (!this._dirty && (this._dirty = true) && q.push(this)===1) (0, Component.debounce)(process);
};

Component.prototype.forceUpdate = function(callback) {
	if (this.base!=null) {
		diff(this.base, this.base.parentNode, this._vnode, this._vnode, this.context, false, true, null, 1, []);
	}
	if (callback!=null) callback();
};

let q = [];

// const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
// Component.debounce = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
// Component.debounce = setTimeout;

export function enqueueRender(c) {
	// console.log('enqueueRender', c.id, q.length===0, c._dirty);
	if (!c._dirty && (c._dirty = true) && q.push(c) === 1) {
		// (0, Component.debounce)(process);
		(Component.debounce || defer)(process);
		// (Component.debounce || setTimeout)(process);
		// defer(process);
		// (Component.debounce || setTimeout)(process);
		// (0, Component.debounce || defer)(process);
	}

	// if (!c._dirty) {
	// 	c._dirty = true;
	// 	if (diffLevel!==0) {
	// 		q.push(c);
	// 	}
	// 	else {
	// 		diff(c.base, c.base.parentNode, c._vnode, c._vnode, c.context);
	// 	}
	// }

	// if (!c._dirty && (c._dirty = true) && q.push(c) === 1) {
	// 	requestAnimationFrame(process);
	// }
	// if (q.push(c) === 1) setTimeout(process);
}

function process() {
	// console.log('process queue', q.map(c => c.context.__depth)+' ');

	// requestIdleCallback( (c) => {
	// 	let p;
	// 	while (c.timeRemaining()>0 && (p=q.pop())) {
	// 		if (p._dirty) p.forceUpdate();
	// 	}
	// 	if (q.length) process();
	// });

	let p;
	while ((p=q.pop())) {
		if (p._dirty) p.forceUpdate();
	}

	// let time = Date.now(), len=q.length, i=len;
	// while (i--) {
	// 	if (Date.now() - time > 2) break;
	// 	if (q[i]._dirty) q[i].forceUpdate();
	// }
	// q.splice(i, len-i);
	// if (i>=0) (0, Component.debounce)(process);

	// while ( Date.now() - time < 5 && (p=q.pop())) {
	// 	if (p._dirty) p.forceUpdate();
	// }
	// if (q.length) (0, Component.debounce)(process);

	// let current = q;
	// q = [];
	// for (let i=current.length; i--; ) {
	// 	if (current[i]._dirty) {
	// 		current[i].forceUpdate();
	// 	}
	// }

	// let current = q, p;
	// q = [];
	// while ((p = current.pop())) {
	// 	if (p._dirty) {
	// 		p.forceUpdate();
	// 		// diff(p.base, p.base.parentNode, p._vnode, p._vnode, p.context);
	// 	}
	// }
}

// export { process as processQueue };


import { createElement, Component } from 'ceviche';

const COUNT = 500;
const LOOPS = 6;

// Component.debounce = requestAnimationFrame;

export default class Spiral extends Component {
	state = { x: 0, y: 0, big: false, counter: 0 };

	handleClick = e => {
		console.log('click');
	};

	increment = () => {
		if (this.stop) return;
		// this.setState({ counter: this.state.counter + 1 }, this.increment);
		this.setState({ counter: this.state.counter + 1 });
		// this.forceUpdate();
		requestAnimationFrame(this.increment);
	};

	setMouse({ pageX: x, pageY: y }) {
		this.setState({ x, y });
		return false;
	}

	setBig(big) {
		this.setState({ big });
	}

	componentDidMount() {
		console.log('mount');

		// let touch = navigator.maxTouchPoints > 1;
		let touch = false;

		// set mouse position state on move:
		addEventListener(touch ? 'touchmove' : 'mousemove', e => {
			this.setMouse(e.touches ? e.touches[0] : e);
		});

		// holding the mouse down enables big mode:
		addEventListener(touch ? 'touchstart' : 'mousedown', e => { this.setBig(true); e.preventDefault(); });
		addEventListener(touch ? 'touchend' : 'mouseup', e => this.setBig(false));

		requestAnimationFrame(this.increment);
	}

	componentWillUnmount() {
		console.log('unmout');
		this.stop = true;
	}

	// componentDidUpdate() {
	// 	// invoking setState() in componentDidUpdate() creates an animation loop:
	// 	this.increment();
	// }

	// builds and returns a brand new DOM (every time)
	render(props, { x, y, big, counter }) {
		let max = COUNT + Math.round(Math.sin(counter / 90 * 2 * Math.PI) * COUNT * 0.5),
			cursors = [];

		// the advantage of JSX is that you can use the entirety of JS to "template":
		for (let i = max; i--;) {
			let f = i / max * LOOPS,
				θ = f * 2 * Math.PI,
				m = 20 + i * 2,
				hue = (f * 255 + counter * 10) % 255;
			cursors[i] = (
				<Cursor
					big={big}
					color={'hsl(' + hue + ',100%,50%)'}
					x={(x + Math.sin(θ) * m) | 0}
					y={(y + Math.cos(θ) * m) | 0}
				/>
			);
		}

		return (

				<Cursor label x={x} y={y} big={big} />
				{cursors}

		);
	}
}

/** Represents a single coloured dot. */
class Cursor extends Component {
	// get shared/pooled class object
	getClass(big, label) {
		let cl = 'cursor';
		if (big) cl += ' big';
		if (label) cl += ' label';
		return cl;
	}

	// skip any pointless re-renders
	shouldComponentUpdate(props) {
		for (let i in props) if (i !== 'children' && props[i] !== this.props[i]) return true;
		return false;
	}

	// first argument is "props", the attributes passed to <Cursor ...>
	render({ x, y, label, color, big }) {
		let inner = null;
		if (label) inner = {x},{y};
		return (
			<div
				class={this.getClass(big, label)}
				style={{
					transform: `translate(${x || 0}px, ${y || 0}px) scale(${big?2:1})`,
					// transform: `translate3d(${x || 0}px, ${y || 0}px, 0) scale(${big?2:1})`,
					borderColor: color
				}}
				// style={{ left: x || 0, top: y || 0, borderColor: color }}
			>{inner}

		);
	}
}

// Addendum: disable dragging on mobile
addEventListener('touchstart', e => (e.preventDefault(), false));


import { createElement as h } from '../../src/index';
// import { VNode } from '../../src/vnode';
import { expect } from 'chai';

/*eslint-env browser, mocha */

/** @jsx h */

// const buildVNode = (nodeName, attributes, children=[]) => ({
// 	nodeName,
// 	children,
// 	attributes,
// 	key: attributes && attributes.key
// });

describe('h(jsx)', () => {
	it('should return a VNode', () => {
		let r;
		expect( () => r = h('foo') ).not.to.throw();
		expect(r).to.be.an('object');
		// expect(r).to.be.an.instanceof(VNode);
		expect(r).to.have.property('tag', 'foo');
		expect(r).to.have.property('props').that.eql({});
		// expect(r).to.have.deep.property('props.children').that.eql(null);
	});

	it('should preserve raw props', () => {
		let props = { foo: 'bar', baz: 10, func: () => {} },
			r = h('foo', props);
		expect(r).to.be.an('object')
			.with.property('props')
			.that.deep.equals(props);
	});

	it('should support element children', () => {
		let kid1 = h('bar');
		let kid2 = h('baz');
		let r = h(
			'foo',
			null,
			kid1,
			kid2
		);

		expect(r).to.be.an('object')
			.with.nested.deep.property('props.children', [
				kid1,
				kid2
			]);
	});

	it('should support multiple element children, given as arg list', () => {
		let kid1 = h('bar');
		let kid3 = h('test');
		let kid2 = h('baz', null, kid3);

		let r = h(
			'foo',
			null,
			kid1,
			kid2
		);

		expect(r).to.be.an('object')
			.with.nested.deep.property('props.children', [
				kid1,
				kid2
			]);
	});

	it('should handle multiple element children, given as an array', () => {
		let kid1 = h('bar');
		let kid3 = h('test');
		let kid2 = h('baz', null, kid3);

		let r = h(
			'foo',
			null,
			[
				kid1,
				kid2
			]
		);

		expect(r).to.be.an('object')
			.with.nested.deep.property('props.children', [
				kid1,
				kid2
			]);
	});

	it('should support nested children', () => {
		const m = x => h(x);
		expect(
			h('foo', null, m('a'), [m('b'), m('c')], m('d'))
		).to.have.nested.property('props.children')
			.that.eql([m('a'), [m('b'), m('c')], m('d')]);

		expect(
			h('foo', null, [m('a'), [m('b'), m('c')], m('d')])
		).to.have.nested.property('props.children')
			.that.eql([m('a'), [m('b'), m('c')], m('d')]);

		expect(
			h('foo', { children: [m('a'), [m('b'), m('c')], m('d')] })
		).to.have.nested.property('props.children')
			.that.eql([m('a'), [m('b'), m('c')], m('d')]);

		expect(
			h('foo', { children: [[m('a'), [m('b'), m('c')], m('d')]] })
		).to.have.nested.property('props.children')
			.that.eql([[m('a'), [m('b'), m('c')], m('d')]]);

		expect(
			h('foo', { children: m('a') })
		).to.have.nested.property('props.children').that.eql(m('a'));

		expect(
			h('foo', { children: 'a' })
		).to.have.nested.property('props.children').that.eql('a');
	});

	it('should support text children', () => {
		let r = h(
			'foo',
			null,
			'textstuff'
		);

		expect(r).to.be.an('object')
			.with.nested.property('props.children')
			.that.equals('textstuff');
	});

	it('should NOT merge adjacent text children', () => {
		let r = h(
			'foo',
			null,
			'one',
			'two',
			h('bar'),
			'three',
			h('baz'),
			h('baz'),
			'four',
			null,
			'five',
			'six'
		);

		expect(r).to.be.an('object')
			.with.nested.property('props.children')
			.that.deep.equals([
				'one',
				'two',
				h('bar'),
				'three',
				h('baz'),
				h('baz'),
				'four',
				null,
				'five',
				'six'
			]);
	});

	it('should not merge nested adjacent text children', () => {
		let r = h(
			'foo',
			null,
			'one',
			['two', null, 'three'],
			null,
			['four', null, 'five', null],
			'six',
			null
		);

		expect(r).to.be.an('object')
			.with.nested.property('props.children')
			.that.deep.equals([
				'one',
				['two', null, 'three'],
				null,
				['four', null, 'five', null],
				'six',
				null
			]);
	});
	it('should not merge children that are boolean values', () => {
		let r = h(
			'foo',
			null,
			'one',
			true,
			'two',
			false,
			'three'
		);

		expect(r).to.be.an('object')
			.with.nested.property('props.children')
			.that.deep.equals(['one',true,'two',false,'three']);
	});

	it('should not merge children of components', () => {
		let Component = ({ children }) => children;
		let r = h(Component, null, 'x', 'y');

		expect(r).to.be.an('object')
			.with.nested.property('props.children')
			.that.deep.equals(['x', 'y']);
	});
});


/*global coverage, ENABLE_PERFORMANCE, NODE_ENV*/
/*eslint no-console:0*/
/** @jsx createElement */

global._ = require('lodash');
const Benchmark = global.Benchmark = require('benchmark');
import { setupScratch, teardown } from './helpers';
const { createElement, render } = require(NODE_ENV==='production' ? '../../dist/ceviche.min.js' : '../../dist/ceviche');
const MULTIPLIER = ENABLE_PERFORMANCE ? (coverage ? 5 : 1) : 999999;

function bench(benches, callback) {
	return new Promise(resolve => {
		const suite = new Benchmark.Suite();

		let i = 0;
		Object.keys(benches).forEach(name => {
			let run = benches[name];
			suite.add(name, () => {
				run(++i);
			});
		});

		suite.on('complete', () => {
			const result = {
				fastest: suite.filter('fastest')[0],
				results: [],
				text: ''
			};
			const useKilo = suite.filter(b => b.hz<10000 ).length === 0;
			suite.forEach((bench, index) => {
				let r = {
					name: bench.name,
					slowdown: bench.name===result.fastest.name ? 0 : (result.fastest.hz - bench.hz) / result.fastest.hz * 100 |0,
					hz: bench.hz.toFixed(bench.hz < 100 ? 2 : 0),
					rme: bench.stats.rme.toFixed(2),
					size: bench.stats.sample.length,
					error: bench.error ? String(bench.error) : undefined
				};
				result.text += `\n  ${r.name}: ${useKilo ? `${r.hz/1000|0} kHz` : `${r.hz} Hz`}${r.slowdown ? ` (-${r.slowdown}%)` : ''}`;
				result.results[index] = result.results[r.name] = r;
			});
			resolve(result);
			if (callback) callback(result);
		});
		suite.run({ async: true });
	});
}

describe('benchmarks', function() {
	let scratch;

	this.timeout(100000);

	before( () => {
		if (coverage) {
			console.warn('WARNING: Code coverage is enabled, which dramatically reduces performance. Do not pay attention to these numbers.');
		}
	});

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('in-place text update', done => {
		const parent = document.createElement('div');
		parent.id = 'preact';
		scratch.appendChild(parent);

		function component(randomValue) {
			return (

Preact {randomValue}

==={randomValue}===

			);
		}
		render(component(0), parent);

		const parent2 = document.createElement('div');
		parent2.id = 'vanilla';
		let div, h1, h2, text1, text2;
		parent2.appendChild(div = document.createElement('div'));
		div.appendChild(h2 = document.createElement('h2'));
		h2.appendChild(document.createTextNode('Vanilla '));
		h2.appendChild(text1 = document.createTextNode('0'));
		div.appendChild(h1 = document.createElement('h1'));
		h1.appendChild(document.createTextNode('==='));
		h1.appendChild(text2 = document.createTextNode('0'));
		h1.appendChild(document.createTextNode('==='));
		scratch.appendChild(parent2);

		bench({
			vanilla(i) {
				const t = i%1000;
				text1.data = '' + t;
				text2.data = '' + t;
			},
			ceviche(i) {
				render(component(i % 1000), parent);
			}
		}, ({ text, results }) => {
			const THRESHOLD = 10 * MULTIPLIER;
			// const slowdown = Math.sqrt(results.ceviche.hz * results.vanilla.hz);
			const slowdown = results.vanilla.hz / results.ceviche.hz;
			console.log(`in-place text update is ${slowdown.toFixed(2)}x slower:` + text);
			expect(slowdown).to.be.below(THRESHOLD);
			done();
		});
	});
});


c.base = diff(dom, parent, vnode, prev, context, isSvg, append, excessChildren, 0, mounts);

// context.__depth = (context.__depth || 0) + 1;

keep keep replace keep keep keep keep keep

c.base = diff(dom, parent, vnode, prev, context, isSvg, append, excessChildren, false, mounts);

import { EMPTY_OBJ, EMPTY_ARR } from './constants';
import { diff } from './diff/index';
import { diffChildren } from './diff/children';
import { coerceToVNode } from './create-element';

export function render(vnode, parent) {
	let oldTree = parent._previousVTree;
	if (oldTree) diff(oldTree._el, parent, parent._previousVTree = coerceToVNode(vnode), oldTree, EMPTY_OBJ, false, true, null, 1, [] );
	else hydrate(vnode, parent);
}

export function hydrate(vnode, parent) {
	parent._previousVTree = vnode = coerceToVNode(vnode);
	diffChildren(parent, [vnode], EMPTY_ARR, EMPTY_OBJ, false, EMPTY_ARR.slice.call(parent.childNodes), 1, []);
}


import { createElement, Component } from 'ceviche';

let counter = 0;

export default class TodoList extends Component {
	state = { todos: [], text: '' };

	setText = e => {
		this.setState({ text: e.target.value });
	};

	addTodo = () => {
		let { todos, text } = this.state;
		todos = todos.concat({ text, id: ++counter });
		this.setState({ todos, text: '' });
	};

	removeTodo = e => {
		let id = e.target.getAttribute('data-id');
		this.setState({ todos: this.state.todos.filter( t => t.id!=id ) });
	};

	render({ }, { todos, text }) {
		return (
			<form onSubmit={this.addTodo} action="javascript:">
				<input value={text} onInput={this.setText} />
				<button type="submit">Add</button>

					<TodoItems todos={todos} removeTodo={this.removeTodo}/>

			</form>
		);
	}
}

class TodoItems extends Component {
	render({ todos, removeTodo }) {
		return todos.map( todo => (

				<button onClick={removeTodo} data-id={todo.id}>×</button>
				{' '}
				{todo.text}

		));
	}
}


/*global coverage, ENABLE_PERFORMANCE, NODE_ENV*/
/*eslint no-console:0*/
/** @jsx h */
import { setupScratch, teardown } from './helpers';
let { createElement: h, Component, render } = require(NODE_ENV==='production' ? '../../dist/ceviche.min.js' : '../../dist/ceviche');

const MULTIPLIER = ENABLE_PERFORMANCE ? (coverage ? 5 : 1) : 999999;

let now = typeof performance!=='undefined' && performance.now ? () => performance.now() : () => +new Date();

function loop(iter, time) {
	let start = now(),
		count = 0;
	while ( now()-start < time ) {
		count++;
		iter();
	}
	return count;
}

function benchmark(iter, callback) {
	let a = 0; // eslint-disable-line no-unused-vars
	function noop() {
		try { a++; }
		finally { a += Math.random(); }
	}

	// warm
	for (let i=100; i--; ) noop(), iter();

	let count = 2,
		time = 500,
		passes = 0,
		noops = loop(noop, time),
		iterations = 0;

	function next() {
		iterations += loop(iter, time);
		setTimeout(++passes===count ? done : next, 10);
	}

	function done() {
		let ticks = Math.round(noops / iterations * count),
			hz = iterations / count / time * 1000,
			message = `${hz|0}/s (${ticks} ticks)`;
		callback({ iterations, noops, count, time, ticks, hz, message });
	}

	next();
}

describe('performance', function() {
	let scratch;

	this.timeout(10000);

	before( () => {
		if (coverage) {
			console.warn('WARNING: Code coverage is enabled, which dramatically reduces performance. Do not pay attention to these numbers.');
		}
	});

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should rerender without changes fast', done => {
		let jsx = (

a {'b'} c {0} d

						Foo
						Bar

					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<button-bar>
							<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
							<button style="top:0 ; right: 20">Poor CSS</button>
							<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
							<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
						</button-bar>
					</form>

		);

		let root;
		benchmark( () => {
			root = render(jsx, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: empty diff: ${message}`);
			expect(ticks).to.be.below(150 * MULTIPLIER);
			done();
		});
	});

	it('should rerender repeated trees fast', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		class ButtonBar extends Component {
			render() {
				return (
					<button-bar>
						<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
						<Button style="top:0 ; right: 20">Poor CSS</Button>
						<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
						<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
					</button-bar>
				);
			}
		}
		class Button extends Component {
			render(props) {
				return <button {...props} />;
			}
		}
		class Main extends Component {
			render() {
				return <Form />;
			}
		}
		class Root extends Component {
			render() {
				return (

				);
			}
		}
		class Empty extends Component {
			render() {
				return
;
			}
		}
		class Parent extends Component {
			render({ child: C }) {
				return <C />;
			}
		}

		let root;
		benchmark( () => {
			root = render(<Parent child={Root} />, scratch, root);
			root = render(<Parent child={Empty} />, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: repeat diff: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should construct large VDOM trees fast', done => {
		const FIELDS = [];
		for (let i=100; i--; ) FIELDS.push((i*999).toString(36));

		let out = [];
		function digest(vnode) {
			out.push(vnode);
			out.length = 0;
		}
		benchmark( () => {
			digest(

a {'b'} c {0} d

							Foo
							Bar

						<form onSubmit={() => {}}>
							<input type="checkbox" checked />
							<input type="checkbox" />
							<fieldset>
								{ FIELDS.map( field => (

										{field}:
										<input placeholder={field} />

								)) }
							</fieldset>
							<button-bar>
								<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
								<button style="top:0 ; right: 20">Poor CSS</button>
								<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
								<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
							</button-bar>
						</form>

			);
		}, ({ ticks, message }) => {
			console.log(`PERF: large VTree: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should mutate styles/properties quickly', done => {
		let counter = 0;
		const keyLooper = n => c => c % n ? `${c}px` : c;
		const get = (obj, i) => obj[i%obj.length];
		const CLASSES = ['foo', 'foo bar', '', 'baz-bat', null];
		const STYLES = [];
		const MULTIVALUE = ['0 1px', '0 0 1px 0', '0', '1px', '20px 10px', '7em 5px', '1px 0 5em 2px'];
		const STYLEKEYS = [
			['left', keyLooper(3)],
			['top', keyLooper(2)],
			['margin', c => get(MULTIVALUE, c).replace('1px', c+'px')],
			['padding', c => get(MULTIVALUE, c)],
			['position', c => c%5 ? c%2 ? 'absolute' : 'relative' : null],
			['display', c => c%10 ? c%2 ? 'block' : 'inline' : 'none'],
			['color', c => `rgba(${c%255}, ${255 - c%255}, ${50+c%150}, ${c%50/50})`],
			['border', c => c%5 ? `${c%10}px ${c%2?'solid':'dotted'} ${STYLEKEYS[6][1](c)}` : '']
		];
		for (let i=0; i<1000; i++) {
			let style = {};
			for (let j=0; j (

				<input type="checkbox" checked={index%3 == 0} />
				<input value={`test ${index/4|0}`} disabled={index%10 ? null : true} />

p1

p2

p3

p4

		);

		let root, count=0;
		benchmark( () => {
			root = render(app(++count), scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: style/prop mutation: ${message}`);
			expect(ticks).to.be.below(350 * MULTIPLIER);
			done();
		});
	});

	it('should hydrate from SSR quickly', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		const ButtonBar = () => (
			<button-bar>
				<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
				<Button style="top:0 ; right: 20">Poor CSS</Button>
				<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
				<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
			</button-bar>
		);
		class Button extends Component {
			handleClick() {}
			render(props) {
				return <button onClick={this.handleClick} {...props} />;
			}
		}
		const Main = () => <Form />;
		class App extends Component {
			render() {
				return (

				);
			}
		}

		render(<App />, scratch, scratch.firstChild);
		let html = scratch.innerHTML;

		benchmark( () => {
			scratch.innerHTML = html;
			render(<App />, scratch, scratch.firstChild);
		}, ({ ticks, message }) => {
			console.log(`PERF: SSR Hydrate: ${message}`);
			expect(ticks).to.be.below(3000 * MULTIPLIER);
			done();
		});
	});
});


dom = newTree._el = diffElementNodes(dom, parent, newTree, oldTree, context, isSvg, excessChildren, 0, mounts);

dom = newTree._el = diffElementNodes(dom, parent, newTree, oldTree, context, isSvg, excessChildren, false, mounts);


import { interpolateViridis } from 'd3-scale';

Math.deg = function(radians) {
	return radians * (180 / Math.PI);
};

const memoizedCalc = function () {
	const memo = {};

	const key = ({ w, heightFactor, lean }) => `${w}-${heightFactor}-${lean}`;

	return (args) => {
		let memoKey = key(args);

		if (memo[memoKey]) {
			return memo[memoKey];
		}

		let { w, heightFactor, lean } = args;
		let trigH = heightFactor*w;

		let result = {
			nextRight: Math.sqrt(trigH**2 + (w * (.5+lean))**2),
			nextLeft: Math.sqrt(trigH**2 + (w * (.5-lean))**2),
			A: Math.deg(Math.atan(trigH / ((.5-lean) * w))),
			B: Math.deg(Math.atan(trigH / ((.5+lean) * w)))
		};

		memo[memoKey] = result;
		return result;
	};
}();

export default function Pythagoras({ w,x, y, heightFactor, lean, left, right, lvl, maxlvl }) {
	if (lvl >= maxlvl || w < 1) {
		return null;
	}

	const { nextRight, nextLeft, A, B } = memoizedCalc({
		w,
		heightFactor,
		lean
	});

	let rotate = '';

	if (left) {
		rotate = `rotate(${-A} 0 ${w})`;
	}
	else if (right) {
		rotate = `rotate(${B} ${w} ${w})`;
	}

	return (
		<g transform={`translate(${x} ${y}) ${rotate}`}>
			<rect
				width={w}
				height={w}
				x={0}
				y={0}
				style={{ fill: interpolateViridis(lvl/maxlvl) }}
			/>

			<Pythagoras
				w={nextLeft}
				x={0} y={-nextLeft}
				lvl={lvl+1} maxlvl={maxlvl}
				heightFactor={heightFactor}
				lean={lean}
				left
			/>

			<Pythagoras
				w={nextRight}
				x={w-nextRight} y={-nextRight}
				lvl={lvl+1} maxlvl={maxlvl}
				heightFactor={heightFactor}
				lean={lean}
				right
			/>
		</g>
	);
}


export default function logger() {
	let calls = {};
	let lock = true;

	function serialize(obj) {
		if (obj instanceof Text) return '#text';
		if (obj instanceof Element) return `<${obj.localName}>`;
		if (obj===document) return 'document';
		return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, '');
	}

	function count(key) {
		if (lock===true) return;
		calls[key] = (calls[key] || 0) + 1;
	}

	function logCall(obj, method, name) {
		let old = obj[method];
		obj[method] = function() {
			let c = '';
			for (let i=0; i<arguments.length; i++) {
				if (c) c += ', ';
				c += serialize(arguments[i]);
			}
			count(`${serialize(this)}.${method}(${c})`);
			return old.apply(this, arguments);
		};
	}

	logCall(document, 'createElement');
	logCall(document, 'createElementNS');
	logCall(Element.prototype, 'remove');
	logCall(Element.prototype, 'appendChild');
	logCall(Element.prototype, 'removeChild');
	logCall(Element.prototype, 'insertBefore');
	logCall(Element.prototype, 'replaceChild');
	logCall(Element.prototype, 'setAttribute');
	logCall(Element.prototype, 'setAttributeNS');
	logCall(Element.prototype, 'removeAttribute');
	logCall(Element.prototype, 'removeAttributeNS');
	let d = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');
	Object.defineProperty(Text.prototype, 'nodeValue', {
		get() {
			let value = d.get.call(this);
			count(`get #text.nodeValue`);
			return value;
		},
		set(v) {
			count(`set #text.nodeValue`);
			return d.set.call(this, v);
		}
	});

	let root;
	function setup() {
		lock = true;
		root = document.createElement('table');
		root.style.cssText = 'position: fixed; right: 0; top: 0; z-index:999; background: #000; font-size: 12px; color: #FFF; opacity: 0.9; white-space: nowrap; pointer-events: none;';
		let header = document.createElement('thead');
		header.innerHTML = '
Stats
';
		root.tableBody = document.createElement('tbody');
		root.appendChild(root.tableBody);
		root.appendChild(header);
		document.body.appendChild(root);
		lock = false;
	}

	let rows = {};
	function createRow(id) {
		let row = document.createElement('tr');
		row.key = document.createElement('td');
		row.key.textContent = id;
		row.appendChild(row.key);
		row.value = document.createElement('td');
		row.value.textContent = ' ';
		row.appendChild(row.value);
		root.tableBody.appendChild(row);
		return rows[id] = row;
	}

	function insertInto(parent) {
		parent.appendChild(root);
	}

	function remove() {
		clearInterval(updateTimer);
	}

	function update() {
		lock = true;
		for (let i in calls) {
			if (calls.hasOwnProperty(i)) {
				let row = rows[i] || createRow(i);
				row.value.firstChild.nodeValue = calls[i];
			}
		}
		lock = false;
	}

	let updateTimer = setInterval(update, 50);

	setup();
	return { insertInto, update, remove };
}


import { assign } from './util';
import { EMPTY_ARR } from './constants';
// import { createElement, createVNode } from './create-element';

/**
 * Clones the given VNode, optionally adding attributes/props and replacing its children.
 * @param {VNode} vnode		The virutal DOM element to clone
 * @param {Object} props	Attributes/props to add when cloning
 * @param {VNode} rest		Any additional arguments will be used as replacement children.
 */
export function cloneElement(vnode, props) {
	// let children = [], i = 2;
	// for ( ; i<arguments.length; i++) children.push(arguments[i]);
	let out = assign({}, vnode);
	out.props = assign(assign({}, out.props), props);
	// if (i>2) out.children = children;
	// if (arguments.length>2) out.props.children = EMPTY_ARR.slice.call(arguments, 2);
	if (arguments.length>2) out.props.children = EMPTY_ARR.slice.call(arguments, 2);
	return out;
	// return createVNode(vnode.type, vnode.tag, assign(assign({}, vnode.props), props), i>2 ? children : null, vnode.text, vnode.key);

	// return createElement(
	// 	vnode.tag,
	// 	assign(assign({}, vnode.props), props),
	// 	arguments.length>2 ? [].slice.call(arguments, 2) : vnode.children
	// );
}

// export function cloneElement(vnode, props) {
// 	return createElement(
// 		vnode.tag,
// 		assign(assign({}, vnode.props), props),
// 		arguments.length>2 ? [].slice.call(arguments, 2) : vnode.children
// 	);
// }

// export function cloneElement(tag, props) {
// 	let vnode = tag;
// 	arguments[0] = vnode.tag;
// 	let el = createElement.apply(null, arguments);
// 	for (let i in vnode.props) if (el.props[i]===undefined) el.props[i] = vnode.props[i];
// 	if (arguments.length<3) el.children = vnode.children;
// 	return el;
// }


import { diffChildren } from './diff/children';

import { coerceToVNode } from './create-element';

if (oldTree) diff(oldTree._el, parent, parent._previousVTree = coerceToVNode(vnode), oldTree, EMPTY_OBJ, false, true, null, 1, [] );

export function hydrate(vnode, parent) {

keep keep keep keep keep replace keep keep keep keep

if (oldTree) diff(oldTree._el, parent, parent._previousVTree = coerceToVNode(vnode), oldTree, EMPTY_OBJ, false, true, null, true, [] );


html, body {
	height: 100%;
	margin: 0;
	background: #eee;
	font: 400 16px/1.3 'Helvetica Neue',helvetica,sans-serif;
	text-rendering: optimizeSpeed;
	color: #444;
	overflow: hidden;
}

.app {
	display: block;
	flex-direction: column;
	height: 100%;

	> header {
		flex: 0;
		background: #f9f9f9;
		box-shadow: inset 0 -.5px 0 0 rgba(0,0,0,0.2), 0 .5px 0 0 rgba(255,255,255,0.6);

		nav {
			display: inline-block;
			padding: 4px 7px;

			a {
				display: inline-block;
				margin: 2px;
				padding: 4px 10px;
				background-color: rgba(255,255,255,0);
				border-radius: 1em;
				color: #6b1d8f;
				text-decoration: none;
				// transition: all 250ms ease;
				transition: all 250ms cubic-bezier(.2,0,.4,2);
				&:hover {
					background-color: rgba(255,255,255,1);
					box-shadow: 0 0 0 2px #6b1d8f;
				}
				&.active {
					background-color: #6b1d8f;
					color: white;
				}
			}
		}
	}

	> main {
		flex: 1;
		padding: 10px;
	}
}

h1 {
	margin: 0;
	color: #6b1d8f;
	font-weight: 300;
	font-size: 250%;
}

input, textarea {
	box-sizing: border-box;
	margin: 1px;
	padding: .25em .5em;
	background: #fff;
	border: 1px solid #999;
	border-radius: 3px;
	font: inherit;
	color: #000;
	outline: none;

	&:focus {
		border-color: #6b1d8f;
	}
}

button, input[type="submit"], input[type="reset"], input[type="button"] {
	box-sizing: border-box;
	margin: 1px;
	padding: .25em .8em;
	background: #6b1d8f;
	border: 1px solid #6b1d8f;
	// border: none;
	border-radius: 1.5em;
	font: inherit;
	color: white;
	outline: none;
	cursor: pointer;
}

.cursor {
	position: absolute;
	left: 0;
	top: 0;
	width: 8px;
	height: 8px;
	margin: -5px 0 0 -5px;
	border: 2px solid #F00;
	border-radius: 50%;
	transform-origin: 50% 50%;
	pointer-events: none;
	overflow: hidden;
	font-size: 9px;
	line-height: 25px;
	text-indent: 15px;
	white-space: nowrap;

	&:not(.label) {
		contain: strict;
	}

	&.label {
		overflow: visible;
	}

	// &.big {
	// 	transform: scale(2);
	// 	// width: 24px;
	// 	// height: 24px;
	// 	// margin: -13px 0 0 -13px;
	// }

	.label {
		position: absolute;
		left: 0;
		top: 0;
		//transform: translateZ(0);
		// z-index: 10;
	}
}

.animation-picker {
	position: fixed;
	display: inline-block;
	right: 0;
	top: 0;
	padding: 10px;
	background: #000;
	color: #BBB;
	z-index: 1000;

	select {
		font-size: 100%;
		margin-left: 5px;
	}
}


import { createElement as h, render, Component } from '../../src/index';
import { setupScratch, teardown, setupRerender } from './helpers';

/** @jsx h */

const EMPTY_CHILDREN = [];

describe('Component spec', () => {
	let scratch, rerender;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();
	});

	afterEach(() => {
		teardown(scratch);
	});

	describe('defaultProps', () => {
		it('should apply default props on initial render', () => {
			class WithDefaultProps extends Component {
				constructor(props, context) {
					super(props, context);
					expect(props).to.be.deep.equal({
						fieldA: 1, fieldB: 2,
						fieldC: 1, fieldD: 2
					});
				}
				render() {
					return <div />;
				}
			}
			WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
			render(<WithDefaultProps fieldA={1} fieldB={2} fieldD={2} />, scratch);
		});

		it('should apply default props on rerender', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 1 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: 2 });
				}
				render(props, { i }) {
					return <WithDefaultProps fieldA={1} fieldB={i} fieldD={i} />;
				}
			}
			class WithDefaultProps extends Component {
				constructor(props, context) {
					super(props, context);
					this.ctor(props, context);
				}
				ctor(){}
				componentWillReceiveProps() {}
				render() {
					return <div />;
				}
			}
			WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };

			let proto = WithDefaultProps.prototype;
			sinon.spy(proto, 'ctor');
			sinon.spy(proto, 'componentWillReceiveProps');
			sinon.spy(proto, 'render');

			render(<Outer />, scratch);
			doRender();

			const PROPS1 = {
				fieldA: 1, fieldB: 1,
				fieldC: 1, fieldD: 1
			};

			const PROPS2 = {
				fieldA: 1, fieldB: 2,
				fieldC: 1, fieldD: 2
			};

			expect(proto.ctor).to.have.been.calledWithMatch(PROPS1);
			expect(proto.render).to.have.been.calledWithMatch(PROPS1);

			rerender();

			// expect(proto.ctor).to.have.been.calledWith(PROPS2);
			expect(proto.componentWillReceiveProps).to.have.been.calledWithMatch(PROPS2);
			expect(proto.render).to.have.been.calledWithMatch(PROPS2);
		});

		// @TODO: migrate this to preact-compat
		xit('should cache default props', () => {
			class WithDefaultProps extends Component {
				constructor(props, context) {
					super(props, context);
					expect(props).to.be.deep.equal({
						fieldA: 1, fieldB: 2,
						fieldC: 1, fieldD: 2,
						fieldX: 10
					});
				}
				getDefaultProps() {
					return { fieldA: 1, fieldB: 1 };
				}
				render() {
					return <div />;
				}
			}
			WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
			sinon.spy(WithDefaultProps.prototype, 'getDefaultProps');
			render((
				<div>
					<WithDefaultProps fieldB={2} fieldD={2} fieldX={10} />
					<WithDefaultProps fieldB={2} fieldD={2} fieldX={10} />
					<WithDefaultProps fieldB={2} fieldD={2} fieldX={10} />
				</div>
			), scratch);
			expect(WithDefaultProps.prototype.getDefaultProps).to.be.calledOnce;
		});
	});

	describe('forceUpdate', () => {
		it('should force a rerender', () => {
			let forceUpdate;
			class ForceUpdateComponent extends Component {
				componentWillUpdate() {}
				componentDidMount() {
					forceUpdate = () => this.forceUpdate();
				}
				render() {
					return <div />;
				}
			}
			sinon.spy(ForceUpdateComponent.prototype, 'componentWillUpdate');
			sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate');
			render(<ForceUpdateComponent />, scratch);
			expect(ForceUpdateComponent.prototype.componentWillUpdate).not.to.have.been.called;

			forceUpdate();

			expect(ForceUpdateComponent.prototype.componentWillUpdate).to.have.been.called;
			expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called;
		});

		it('should add callback to renderCallbacks', () => {
			let forceUpdate;
			let callback = sinon.spy();
			class ForceUpdateComponent extends Component {
				componentDidMount() {
					forceUpdate = () => this.forceUpdate(callback);
				}
				render() {
					return <div />;
				}
			}
			sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate');
			render(<ForceUpdateComponent />, scratch);

			forceUpdate();

			expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called;
			expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.calledWith(callback);
			expect(callback).to.have.been.called;
		});
	});
});


parent._previousVTree = vnode = coerceToVNode(vnode);

diffChildren(parent, [vnode], EMPTY_ARR, EMPTY_OBJ, false, EMPTY_ARR.slice.call(parent.childNodes), 1, []);

diffChildren(parent, [vnode], EMPTY_ARR, EMPTY_OBJ, false, EMPTY_ARR.slice.call(parent.childNodes), true, []);


root = true

[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[{package.json,.*rc,*.yml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false


import preact, { createElement, Component, render, rerender, options } from '../../src/index';
import { expect } from 'chai';

describe('preact', () => {
	it.skip('should be available as a default export', () => {
		expect(preact).to.be.an('object');
		expect(preact).to.have.property('h', createElement);
		expect(preact).to.have.property('Component', Component);
		expect(preact).to.have.property('render', render);
		expect(preact).to.have.property('rerender', rerender);
		expect(preact).to.have.property('options', options);
	});

	it('should be available as named exports', () => {
		expect(createElement).to.be.a('function');
		expect(Component).to.be.a('function');
		expect(render).to.be.a('function');
		// expect(rerender).to.be.a('function');
		// expect(options).to.exist.and.be.an('object');
	});
});


import { ELEMENT_NODE, TEXT_NODE } from './constants';

export function createElement(tag, props, children) {
	if (props==null) props = {};
	if (arguments.length>3) {
		children = [children];
		for (let i=3; i<arguments.length; i++) {
			children.push(arguments[i]);
		}
	}
	return createVNode(ELEMENT_NODE, tag, props, children, null, props.key);
	// return createVNode(ELEMENT_NODE, tag, props==null ? EMPTY_OBJ : props, children, null, props!=null ? props.key : null);
}

export function Fragment(vnode, props) {
	return vnode.children;
}

// const RECYCLED_VNODES = [];

// export function reclaimVNode(vnode) {
// 	vnode.index = vnode._children = vnode._el = vnode._component = null;
// 	RECYCLED_VNODES.push(vnode);
// }

export function createVNode(type, tag, props, children, text, key) {

	// @TODO this is likely better off in createElement():
	if (type===ELEMENT_NODE) {
		// if (props==null) props = {};
		if (children!=null) props.children = children;
		// children = props.children || (props.children = children);
		if (tag.defaultProps!=null) {
			for (let i in tag.defaultProps) {
				if (props[i]===undefined) props[i] = tag.defaultProps[i];
			}
		}
	}

	// let r = RECYCLED_VNODES.pop();
	// if (r!=null) {
	// 	r.type = type;
	// 	r.tag = tag;
	// 	r.props = props;
	// 	r.children = children;
	// 	r.text = text;
	// 	r.key = key;
	// 	return r;
	// }
	// return { type, tag, props, attributes: props, /*children,*/ text, key, index: null, _children: null, _el: null, _component: null };
	return { type, tag, props, /*children,*/ text, key, index: null, _children: null, _el: null, _component: null };
	// return { type, tag, props, children, text, key, index: null, _children: null, _el: null, _component: null };
}

/**
 * Coerce an untrusted value into a VNode
 * Specifically, this should be used anywhere a user could provide a boolean, string, or number where
 * a VNode or Component is desired instead
 * @param {boolean | string | number | function | object} possibleVNode A possible VNode
 * @returns {object | function}
 */
export function coerceToVNode(possibleVNode) {
	if (typeof possibleVNode === 'boolean') return null;
	if (typeof possibleVNode === 'string' || typeof possibleVNode === 'number') {
		return createVNode(TEXT_NODE, null, null, null, possibleVNode, null);
	}

	return possibleVNode;
}


import React from 'react'

// Feature flag
const ACTIVE = true

const key = 'CARBON_CTA_4'

function Toast() {
  const [open, setState] = React.useState(false)

  React.useEffect(() => {
    window.localStorage.removeItem('CARBON_CTA_2')
    window.localStorage.removeItem('CARBON_CTA_3')
    if (!window.localStorage.getItem(key)) {
      setState(true)
    }
  }, [])

  if (process.env.NODE_ENV !== 'production') {
    return null
  }

  if (!ACTIVE) {
    return null
  }

  if (!open) {
    return null
  }

  function close() {
    setState(false)
    window.localStorage.setItem(key, true)
  }

  return (

Black Lives Matter.

          Help end police violence in America →

        <button className="close-toast" onClick={close}>
          ×
        </button>

  )
}

export default Toast


import React from 'react'
import VisuallyHidden from '@reach/visually-hidden'

import { COLORS } from '../lib/constants'

const Button = ({
  id,
  onClick = () => {},
  background = COLORS.BLACK,
  color = COLORS.SECONDARY,
  textColor,
  hoverBackground = COLORS.HOVER,
  hoverColor,
  disabled,
  selected,
  children,
  border,
  center,
  large,
  flex = 1,
  padding = 0,
  margin = 0,
  title,
  Component = 'button',
  display,
  ...props
}) => (
  <Component id={id} onClick={onClick} disabled={disabled} {...props}>
    {title && <VisuallyHidden>{title}</VisuallyHidden>}
    {children}
    <style jsx>
      {`
        ${Component} {
          display: ${display || 'flex'};
          flex: ${flex};
          background-color: ${background};
          color: ${textColor || color};
          box-shadow: ${border ? `inset 0px 0px 0px ${selected ? 2 : 1}px ${color}` : 'initial'};
          cursor: ${disabled ? 'not-allowed' : 'pointer'};
          outline: none;
          border: none;
          padding: ${padding};
          margin: ${margin};
          border-radius: ${border ? '3px' : 0};
          user-select: none;
          justify-content: ${center ? 'center' : 'initial'};
          align-items: ${center ? 'center' : 'initial'};
          align-self: stretch;
          font-size: ${large ? '14px' : '12px'};
        }

        ${Component}:hover, ${Component}:focus {
          background-color: ${hoverBackground} !important;
          color: ${hoverColor || color};
        }

        ${Component}:focus {
          box-shadow: ${border ? `inset 0px 0px 0px 2px ${color}` : 'initial'};
        }
      `}
    </style>
  </Component>
)

export default Button


.CodeMirror.cm-s-synthwave-84{font-family:Menlo,Consolas,'DejaVu Sans Mono',monospace;font-weight:350;font-size:18px;color:#b6b1b1;background-color:#2b213a}.cm-s-synthwave-84 .CodeMirror-selected{background-color:#1f172e!important}.cm-s-synthwave-84 .CodeMirror-gutter,.cm-s-synthwave-84 .CodeMirror-gutters{border:none;background-color:#011627}.cm-s-synthwave-84 .CodeMirror-linenumber,.cm-s-synthwave-84 .CodeMirror-linenumbers{color:#6d77b3!important;background-color:transparent}.cm-s-synthwave-84 .CodeMirror-lines{color:#b6b1b1!important;background-color:transparent}.cm-s-synthwave-84 .CodeMirror-cursor{border-left:2px solid #fa7b73!important}.cm-s-synthwave-84 .CodeMirror-matchingbracket,.cm-s-synthwave-84 .CodeMirror-matchingtag{border-bottom:2px solid #c792ea;color:#b6b1b1!important;background-color:transparent}.cm-s-synthwave-84 .CodeMirror-nonmatchingbracket{border-bottom:2px solid #fa7b73;color:#b6b1b1!important;background-color:transparent}.cm-s-synthwave-84 .CodeMirror-foldgutter,.cm-s-synthwave-84 .CodeMirror-foldgutter-folded,.cm-s-synthwave-84 .CodeMirror-foldgutter-open,.cm-s-synthwave-84 .CodeMirror-foldmarker{border:none;text-shadow:none;color:#5c6370!important;background-color:transparent}.cm-s-synthwave-84 .CodeMirror-activeline-background{background-color:#2b213a}.cm-s-synthwave-84 .cm-quote{color:#6d77b3;font-style:italic}.cm-s-synthwave-84 .cm-negative{color:#fff5f6;text-shadow:0 0 2px #000,0 0 10px #fc1f2c75,0 0 5px #fc1f2c75,0 0 25px #fc1f2c75}.cm-s-synthwave-84 .cm-positive{color:#fff5f6;text-shadow:0 0 2px #000,0 0 10px #fc1f2c75,0 0 5px #fc1f2c75,0 0 25px #fc1f2c75}.cm-s-synthwave-84 .cm-strong{color:#f97e72;font-weight:700}.cm-s-synthwave-84 .cm-em{color:#f4eee4;font-style:italic;text-shadow:0 0 2px #393a33,0 0 8px #f39f0575,0 0 2px #f39f0575}.cm-s-synthwave-84 .cm-attribute{color:#fff5f6;text-shadow:0 0 2px #000,0 0 10px #fc1f2c75,0 0 5px #fc1f2c75,0 0 25px #fc1f2c75}.cm-s-synthwave-84 .cm-link{color:#f97e72;border-bottom:solid 1px #f97e72}.cm-s-synthwave-84 .cm-keyword{color:#f4eee4;font-style:italic;text-shadow:0 0 2px #393a33,0 0 8px #f39f0575,0 0 2px #f39f0575}.cm-s-synthwave-84 .cm-def{color:#fdfdfd;text-shadow:0 0 2px #001716,0 0 3px #03edf975,0 0 5px #03edf975,0 0 8px #03edf975}.cm-s-synthwave-84 .cm-atom{color:#f97e72}.cm-s-synthwave-84 .cm-number{color:#f97e72}.cm-s-synthwave-84 .cm-property{color:#fdfdfd;text-shadow:0 0 2px #001716,0 0 3px #03edf975,0 0 5px #03edf975,0 0 8px #03edf975}.cm-s-synthwave-84 .cm-qualifier{color:#f97e72}.cm-s-synthwave-84 .cm-variable{color:#f92aad;text-shadow:0 0 2px #100c0f,0 0 5px #dc078e33,0 0 10px #fff3}.cm-s-synthwave-84 .cm-variable-2{color:#f92aad;text-shadow:0 0 2px #100c0f,0 0 5px #dc078e33,0 0 10px #fff3}.cm-s-synthwave-84 .cm-string{color:#ff8b39}.cm-s-synthwave-84 .cm-string-2{color:#ff8b39}.cm-s-synthwave-84 .cm-operator{color:#f4eee4;text-shadow:0 0 2px #393a33,0 0 8px #f39f0575,0 0 2px #f39f0575}.cm-s-synthwave-84 .cm-meta{color:#ff8b39}.cm-s-synthwave-84 .cm-comment{color:#6d77b3;font-style:italic}.cm-s-synthwave-84 .cm-error{color:#fff5f6;text-shadow:0 0 2px #000,0 0 10px #fc1f2c75,0 0 5px #fc1f2c75,0 0 25px #fc1f2c75}


import React from 'react'
import AuthContext from './AuthContext'
import Meta from './Meta'
import Header from './Header'
import Footer from './Footer'
import Announcement from './Announcement'
import LoginButton from './LoginButton'

const COLUMN = `
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
`
class Page extends React.Component {
  render() {
    const { children, enableHeroText, flex } = this.props
    return (

        <Meta />
        <AuthContext>
          <Announcement />

            <LoginButton />

{children}

        </AuthContext>

    )
  }
}

export default Page


padding: 8px 16px;
color: #fff;
border: 1px solid #fff;
border-radius: 2px;
margin-top: -4.6rem;
animation-name: slide;
animation-duration: 600ms;

keep keep keep replace replace keep keep


// Theirs
import React from 'react'
import Router from 'next/router'
import { useAsyncCallback } from 'actionsack'

import Editor from './Editor'
import Toasts from './Toasts'
import { useAPI } from './ApiContext'
import { useAuth } from './AuthContext'

import { THEMES } from '../lib/constants'
import { updateRouteState } from '../lib/routing'
import { getThemes, saveThemes, clearSettings, saveSettings } from '../lib/util'

function onReset() {
  clearSettings()
  updateRouteState(Router, {})

  if (window.navigator && navigator.serviceWorker) {
    navigator.serviceWorker.getRegistrations().then(registrations => {
      registrations.forEach(registration => {
        registration.unregister()
      })
    })
  }
}

function toastsReducer(curr, action) {
  switch (action.type) {
    case 'ADD': {
      if (!curr.find(t => t.children === action.toast.children)) {
        return curr.concat(action.toast)
      }
      return curr
    }
    case 'SET': {
      return action.toasts
    }
  }
  throw new Error('Unsupported action')
}

function EditorContainer(props) {
  const [themes, updateThemes] = React.useState(THEMES)
  const api = useAPI()
  const user = useAuth()
  const [update, { loading }] = useAsyncCallback(api.snippet.update)

  React.useEffect(() => {
    const storedThemes = getThemes(localStorage) || []
    if (storedThemes) {
      updateThemes(currentThemes => [...storedThemes, ...currentThemes])
    }
  }, [])

  React.useEffect(() => {
    saveThemes(themes.filter(({ custom }) => custom))
  }, [themes])

  // XXX use context
  const [snippet, setSnippet] = React.useState(props.snippet || null)
  const [toasts, setToasts] = React.useReducer(toastsReducer, [])

  const snippetId = snippet && snippet.id
  React.useEffect(() => {
    if ('/' + (snippetId || '') === props.router.asPath) {
      return
    }
    window.history.pushState(null, null, '/' + (snippetId || ''))
    // props.router.replace(props.router.asPath, '/' + (snippetId || ''), { shallow: true })
  }, [snippetId, props.router])

  function onEditorUpdate(state) {
    if (loading) {
      return
    }

    if (!user) {
      updateRouteState(props.router, state)
      saveSettings(state)
    } else {
      const updates = state
      if (!snippet) {
        update(snippetId, updates).then(newSnippet => {
          if (newSnippet && newSnippet.id) {
            setSnippet(newSnippet)
            setToasts({
              type: 'ADD',
              toast: { children: 'Snippet saved!', closable: true },
            })
          }
        })
      } else if (snippet.userId === user.uid) {
        update(snippetId, updates).then(() => {
          setToasts({
            type: 'ADD',
            toast: { children: 'Snippet saved!', closable: true },
          })
        })
      }
    }
  }

  return (
    <>
      <Toasts toasts={toasts} />
      <Editor
        {...props}
        themes={themes}
        updateThemes={updateThemes}
        snippet={snippet}
        setSnippet={setSnippet}
        setToasts={setToasts}
        onUpdate={onEditorUpdate}
        onReset={onReset}
      />
    </>
  )
}

export default EditorContainer


/* global cy */
import { editorVisible } from '../support'

// usually we can visit the page before each test
// but these tests use the url, which means wasted page load
// so instead visit the desired url in each test

describe('localStorage', () => {
  const themeDropdown = () => cy.get('.toolbar .dropdown-container').first()

  const pickTheme = (name = 'Blackboard') =>
    themeDropdown()
      .click()
      .contains(name)
      .click()

  it.skip('is empty initially', () => {
    cy.visit('/')
    editorVisible()
    cy.window()
      .its('localStorage')
      .should('have.length', 0)
  })

  it('saves on theme change', () => {
    cy.visit('/')
    editorVisible()
    pickTheme('Blackboard')
    themeDropdown()
      .click()
      .contains('Blackboard')

    cy.wait(1500) // URL updates are debounced

    cy.window()
      .its('localStorage.CARBON_STATE')
      .then(JSON.parse)
      .its('theme')
      .should('equal', 'blackboard')

    // visiting page again restores theme from localStorage
    cy.visit('/')
    pickTheme('Cobalt')
    cy.wait(1500) // URL updates are debounced

    cy.url().should('contain', 't=cobalt')
  })
})


/* fix this to only set max width when necessary (mobile) */

keep keep replace replace replace replace keep keep keep


##### 翻译

        Español

        हिंदी

        Deutsch

        简体中文

        Português

        മലയാളം

        Türkçe

        한국어

        தமிழ்

        Français

        日本語

        Svenska

        Add +

## 简介

你见过推特上面的 [这](https://twitter.com/dan_abramov/status/890191815567175680) [些](https://twitter.com/reactjs/status/890511993261654017) [代码](https://twitter.com/notquiteleo/status/873483329345028096) [图片](https://twitter.com/zeithq/status/805779711154647040) 吗? 虽然这些代码令人印象深刻，但从审美角度来说还有提升空间。Carbon 能够轻松地将你的源码生成漂亮的图片并分享。还等什么呢？用这个新发现的美化工具秀给你的 followers 看吧。

## 特性

- **定制化**：定制选项包括图片的语法主题、窗口样式等
- **快速分享**：一键生成图片并分享
- **从 GitHub gist 导入**：只需要在 url 后面加上 GitHub gist id

## 使用

#### 导入

有几种不同的方法可以将代码导入到 Carbon：

- 把文件拖放到编辑器中
- 在 carbon url 后面添加 GitHub gist id （比如 `carbon.now.sh/你的_gist_id`）
- 直接粘贴代码

#### 定制化

当你把代码导入到 Carbon 后，你可以定制生成的代码图片。定制选项包括语法主题、背景颜色、窗口主题、以及 padding 距离。

#### 导出/分享

当你定制完图片样式后，可以分享到推特上面，或者直接保存。

如果你通过 "Tweet" 按钮分享，Carbon 将自动设置你的图片为公开访问。不过如果你想手动分享图片，可以查看 [如何设置 Twitter 图片公开访问](https://help.twitter.com/en/using-twitter/picture-descriptions)。

如果你在文章中添加 Carbon 图片，注意到图片中的源代码对于无障碍技术来说是不可见的，比如无法放大字号或复制。可以考虑新增一个元素展示文本形式的源代码，比如在图片下方加一个 [详细信息展现元素](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/details)。

#### 安装 Carbon 桌面客户端（离线）

如果你使用的是 Google Chrome 或别的支持 PWA 技术的浏览器，你可以安装 Carbon 并离线使用：

1. 访问 [https://carbon.now.sh](https://carbon.now.sh)
2. 点击浏览器的设置按钮
3. 点击 "安装 Carbon..."

## 社区

浏览社区中的这些优秀项目：

##### 编辑器插件

- [IntelliJ IDEA `carbon-now-sh`](https://plugins.jetbrains.com/plugin/10469-carbon-now-sh) - 在 IntelliJ IDEA 中通过右键菜单导入选中的代码到 Carbon
- [Atom `carbon-now-sh`](https://atom.io/packages/carbon-now-sh) - 在 Atom 中通过 `shift-cmd-A` 快捷键导入当前文件到 Carbon
- [VS Code `carbon-now-sh`](https://marketplace.visualstudio.com/items?itemName=ericadamski.carbon-now-sh) - 在 VS Code 中通过 `carbon` 命令导入当前文件到 Carbon
- [Sublime Text 3 `carbon-now-sh`](https://github.com/molnarmark/carbonSublime) - 在 Sublime Text 3 中通过自定义快捷键导入选中的代码到 Carbon
- [Vim `carbon-now-sh`](https://github.com/kristijanhusak/vim-carbon-now-sh) - 在 Vim/Neovim 中通过 `CarbonNowSh()` 函数导入选中的代码到 Carbon
- [Emacs `carbon-now-sh`](https://github.com/veelenga/carbon-now-sh.el) - 在 Emacs 中通过 `carbon-now-sh` 交互函数导入选中的代码到 Carbon
- [Xcode `carbon-now-sh`](https://github.com/StevenMagdy/CarboNow4Xcode) - 在 `carbon.now.sh` 中打开选中的代码
- [Xcode `nef`](https://github.com/bow-swift/nef-plugin) - 这个 Xcode 扩展程序能让你将选中的代码一键导出成 Carbon 片段

##### 工具

- [CLI `carbon-now-cli`](https://github.com/mixn/carbon-now-cli) - 用 `carbon-now` 命令在 Carbon 中打开一个文件或者直接下载它，包含交互式选项、可选高亮等功能
- [Carbonize](https://itunes.apple.com/us/app/carbonize/id1451177988) - 一个封装并扩展了原生功能的 macOS 程序
- [CodeExpander](https://codeexpander.com) - 是一个智能并具有像 TextExpander 特性的 GitHub Gist 客户端
- [`nef`](https://github.com/bow-swift/nef#-exporting-carbon-code-snippets) - 从 `Xcode Playground` 导出多个 Carbon 代码片段。

##### 库

- [R `carbonate`](https://yonicd.github.io/carbonate/) - 用 `R` 脚本迭代处理图片样式并在 Carbon 中打开或直接下载。

##### 书籍

- ["CS 101 - An Introduction to Computational Thinking"](https://itunes.apple.com/us/book/id1435714196) - Sarbo Roy 写的一本计算机科学书.

## 项目作者

Carbon 项目由 TA 们发起：

- Mike Fix ([@mfix22](https://github.com/mfix22))
- Brian Dennis ([@briandennis](https://github.com/briandennis))
- Jake Dexheimer ([@jakedex](https://github.com/jakedex))

#### License

[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fcarbon-app%2Fcarbon.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fcarbon-app%2Fcarbon?ref=badge_large)

---

## 贡献代码

欢迎 PR！请查看 [贡献指南](/.github/CONTRIBUTING.md) 了解详情。

### 赞助商

你的公司在用 Carbon 吗？考虑一下为此项目的新功能开发、bug 修复等提供赞助。

### 赞助者

喜欢用 Carbon 吗？考虑一下为此项目的新功能开发或改进提供赞助。

### 感谢

[ ▲ Vercel ](https://vercel.com/) 赞助了 Carbon 的服务托管。

### 贡献者

![Contributors image](https://opencollective.com/carbon-app/contributors.svg?width=1024)

#### 所有贡献者

感谢所有这些优秀的贡献者们 ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):

briandennis
💻 📖 🚇 👀

mfix22
💬 💻 🤔

jakedex
💬 💻 🎨 📹

andrewda
💬 💻 🐛 👀

yeskunall
💻 📖 🔧 🐛

stoshfabricius
💻

jkling38
📖

otobrglez
💻

darahak
📖

dom96
💻

elrumordelaluz
💻

cjb
💻

Krzysztof-Cieslak
💻

fernahh
📖

g3r4n
💻

Mat Gadd
🐛 💻

Brad Davies
🐛 💻

Rafael Câmara
💻

Gleb Bahmutov
⚠ 🔧

Iván Munguía
💻

Dillon Mulroy
💻

Nihad Abbasov
💻

Hugo Torzuoli
📖

Bruno C. Couto
💻

Mark Molnar
💻

Takahiko Inayama
💻

François Martin
💻

Raphael Amorim
💻

Camron Flanders
💻

Eric Adamski
💻

Winner Crespo
💻 🎨

Milos
💻 🔧 📖 🌍

Yashu Mittal
💻

Rachel M. Carmena
📖

Miguel Salazar
📖 🌍

Vyom Jain
📖 🌍

racaljk
🌍

Sean
💻

Izabela Borges
🌍

Shinil M S
🌍

Berke Atac
🌍

LEE YONGJUN
🌍

Matthew Nielsen
💻

Boy
📦

Vetrivel Chinnasamy
🌍

Farzad YZ
💻 🤔

Yannick Loriot
🌍

Joel Hanson
💻

Muhammad Muzzammil
💻

souppower
🚇

Masato Urai (@uraway_)
🌍

Fernando
🌍

Megha Sachdev
💻 ⚠

Anudeep Reddy
🚇

Munieru
🌍

Adam Lusted
💻

Jose Noriega
🌍

Merlin Fuchs
🌍

Ramy Majouji
💻

Viktor Nemes
💻

Eric Bailey
💻

Nazeefa
🌍

Pratik Butani
📝

Bakti Aditya
💻

Caleb Taylor
💻

Rogério Munhoz
💻

Technoknol
💻

Tymoteusz Makowski
💻

Nisar Hassan Naqvi
🐛

Ilyas Karim
🐛

Nick Fix
🤔

Melanie Sumner
🤔

aluc
💻

B. Mearns
🤔

Peng Jie
💻

Binyamin Aron Green
💻

Michal
🌍

Quinn Blenkinsop
💻

Dwiferdio Seagal Putra
💻