Repository: pzavolinsky/react-unit Branch: master Commit: 37ea35d6cacf Files: 58 Total size: 280.8 KB Directory structure: gitextract_ljpf4nw2/ ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dist/ │ ├── add-ons/ │ │ ├── debug.js │ │ ├── exclude.js │ │ ├── index.js │ │ ├── mock.js │ │ └── with-context.js │ ├── internal/ │ │ └── test-render-instance.js │ ├── pipeline.js │ ├── react-unit.js │ ├── render-instance.js │ ├── resolver.js │ ├── sizzle-bundle.js │ ├── types.js │ ├── unit-component.js │ ├── utils.js │ └── wrapper.js ├── gulpfile.js ├── lib/ │ ├── sizzle-bundle.js │ └── sizzle.js ├── package.json ├── src/ │ ├── add-ons/ │ │ ├── debug.ts │ │ ├── exclude.ts │ │ ├── index.ts │ │ ├── mock.ts │ │ └── with-context.ts │ ├── defs.d.ts │ ├── pipeline.ts │ ├── react-unit.ts │ ├── render-instance.ts │ ├── resolver.ts │ ├── sizzle-bundle.d.ts │ ├── sizzle-bundle.js │ ├── types.ts │ ├── unit-component.ts │ ├── utils.ts │ └── wrapper.ts ├── test/ │ ├── create-component-interleaved.jsx │ ├── create-component-shallow.jsx │ ├── create-component.jsx │ ├── events.jsx │ ├── exclude.jsx │ ├── find-by-component.jsx │ ├── find-by-query.jsx │ ├── find-by-ref.jsx │ ├── find-by.jsx │ ├── internal/ │ │ └── render-instance.jsx │ ├── mock.jsx │ ├── render-new.jsx │ ├── stateful.jsx │ ├── stateless.jsx │ ├── text.jsx │ └── with-context.jsx ├── tsconfig.json ├── tslint.json └── typings.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules dist/test* *.tgz ================================================ FILE: .npmignore ================================================ dist/test* lib src test *.tgz typings gulpfile.js *.json ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Patricio Zavolinsky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ react-unit ========== React Unit is a lightweight unit test library for ReactJS with very few (js-only) dependencies. By using `react-unit` you can run your ReactJS unit tests directly from node or gulp without having to install any heavyweight external dependencies (such as `jsdom`, `phantomjs`, the python runtime, etc.). Note: for testing simple components you might also be interested in [react-cucumber](https://github.com/pzavolinsky/react-cucumber). Installation ------------ ``` npm install --save-dev react-unit ``` and then, in your tests: ```javascript var React = require('react'); var createComponent = require('react-unit'); describe('MyComponent', () => { it('should echo the value', () => { var component = createComponent(); var input = component.findByQuery('input')[0]; expect(input.props.value).toBe('hello, world!'); }); it('should trigger events', () => { var changedValue; function onChange(e) { changedValue = e.target.value; } var component = createComponent(); var input = component.findByQuery('input')[0]; input.onChange({target:{value: 'hi, everyone!'}}); expect(changedValue).toBe('hi, everyone!'); }); }); ``` Note that, while this example is using Jasmine, `react-unit` should work with any other test language of your choice. Usage ----- To use `react-unit` just require the `createComponent` function: ```javascript var createComponent = require('react-unit'); ``` Then use it to create your component: ```javascript var component = createComponent(); ``` or (if, for some reason you are not into JSX): ```javascript var component = createComponent(React.createElement(MyComponent, { value: "hello, world!" })); ``` Now that you have a representation of your component you can use it to find actual HTML elements calling `findByQuery`: ```javascript var allInputs = component.findByQuery('input'); var allRows = component.findByQuery('.row'); var allFirstNames = component.findByQuery('[name=firstName]'); ``` By now you probably noted that `findByQuery` takes something suspiciously similar to jQuery selectors. This is not an innocent coincidence, `react-unit` is bundled with the amazing [jQuery Sizzle](https://github.com/jquery/sizzle) to allow you to search your react DOM using query selectors. In addition to `findByQuery` you can use `findBy` to test every element using a custom function: ```javascript var all = component.findBy(function() { return true; }); // () => true var moreThanTwo = component.findBy(function(c) { return c.props.value > 2 }); ``` To find elements by their `ref` attribute, you can use the `findByRef` method: ```javascript var allMyRefs = component.findByRef('myRef'); ``` If you want to find a component using a component variable instead of a string expression, you can use `findByComponent`: ```javascript var component = createComponent.shallow(); // Note: the .shallow! // or var component = createComponent.interleaved(); var children = component.findByComponent(ChildComponent); ``` Note that `findByComponent` only works with `shallow` and `interleaved` rendering modes. See [Rendering Modes](#rendering-modes) below for more details. If you want to test event handling, you can bind a handler to your component: ```javascript var changeEvent; function handler(e) { changeEvent = e; } var component = createComponent(); ``` Then find and interact with any element in the component: ```javascript component.findByQuery('some selector')[0].onChange('some event'); ``` Finally assert the event: ```javascript expect(changeEvent).toBe('some event'); ``` If at any point you want to inspect the rendered component you can use: ```javascript console.log(component.dump()); ``` API Reference ------------- #### Creating components ```javascript // createComponent :: ReactElement -> Component createComponent = (reactElement) => Component ``` Renders `reactElement` using the deep rendering strategy (see [Rendering Modes](#rendering-modes) for more details). Returns the rendered `Component`. This method produces a component tree that is somewhat similar to applying `ReactDOM.render`. For example: ```javascript var createComponent = require('react-unit'); var component = createComponent(); ``` More examples in [test/create-component.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/create-component.jsx). ---
```javascript // createComponent.shallow :: ReactElement -> Component createComponent.shallow = (reactElement) => Component ``` Renders `reactElement` using the shallow rendering strategy (see [Rendering Modes](#rendering-modes) for more details). Returns the rendered `Component`. This method produces a shallow component tree. That is, it renders the root component and all the children HTML nodes, stopping at the first child component level. For example: ```javascript var createComponent = require('react-unit'); var component = createComponent.shallow(); ``` More examples in [test/create-component-shallow.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/create-component-shallow.jsx). ---
```javascript // createComponent.interleaved :: ReactElement -> Component createComponent.interleaved = (reactElement) => Component ``` Renders `reactElement` using the interleaved rendering strategy (see [Rendering Modes](#rendering-modes) for more details). Returns the rendered `Component`. This method produces a component tree that interleaves react components and actual rendered components. For example: ```javascript var createComponent = require('react-unit'); var component = createComponent.interleaved(); ``` More examples in [test/create-component-interleaved.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/create-component-interleaved.jsx). ---
#### Finding components ```javascript // findByQuery :: String -> [Component] component.findByQuery => (sizzleExpression) => [Components] ``` Returns all the descendant elements of `component` matching `sizzleExpression`. For example: ```javascript var inputs = component.findByQuery('input'); ``` More examples in [test/find-by-query.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/find-by-query.jsx). ---
```javascript // findByComponent :: ReactElement -> [Component] component.findByComponent => (reactElement) => [Components] ``` Returns all the descendant elements of `component` of type `reactElement`. Note that `findByComponent` only works with `shallow` and `interleaved` rendering modes. See [Rendering Modes](#rendering-modes) below for more details. For example: ```javascript // assuming: var MyItem = React.createClass({ ... }); var items = component.findByComponent(MyItem); ``` More examples in [test/find-by-component.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/find-by-component.jsx). ---
```javascript // findBy :: (Component -> bool) -> [Component] component.findBy => (fn) => [Components] ``` Returns all the descendant elements of `component` for whom `fn` returns `true`. For example: ```javascript var moreThanTwos = component.findBy(c => c.props.value > 2); ``` More examples in [test/find-by.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/find-by.jsx). ---
```javascript // findByRef :: String -> [Component] component.findBy => (ref) => [Components] ``` Returns all the descendant elements of `component` matching the `ref` attribute. For example: ```javascript var allMyRefs = component.findByRef('myRef'); ``` More examples in [test/find-by-ref.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/find-by-ref.jsx). ---
#### Inspecting components ```javascript // dump :: () -> String component.dump => () => String ``` Returns a string representation of the pseudo-HTML of the component. This method is very useful for troubleshooting broken tests. For example: ```javascript var html = component.dump(); // or console.log(component.dump()); ``` ---
```javascript component.texts // :: [String] component.text // :: String ``` Return the text of all the descendant elements of `component`. `texts` is a flat array containing the texts of every descendant element in depth order. `text` behaves like `DOMNode.textContent` (i.e. `component.texts.join('')`). Some examples in [test/text.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/text.jsx). ---
```javascript // key :: Object component.props ``` The `props` object of `component`. ---
```javascript // key :: String component.key ``` The `key` of `component`. ---
```javascript // ref :: String component.ref ``` The `ref` of `component`. ---
Rendering Modes --------------- #### Deep rendering (default behavior) By default `react-unit` will use a deep (recursive) rendering strategy. This produces an output that is very similar to that of `ReactDOM.render`. For example, given: ```jsx var Person = React.createClass({ render: function() { var children = React.Children.map(this.props.children, (c,i) =>
  • {c}
  • ); return

    {this.props.name}

      {children}
    } }); ``` Calling `createComponent` in a composite component: ```jsx var component = createComponent( ); ``` Results in a representation of the following HTML: ```html

    Homer

    • Bart

      • Lisa

        • Maggie

          ``` In other words, the output is the HTML that results of calling the render method of every component. Note that, as a side-effect of deep rendering, component tags (e.g. ``) were erased from the HTML representation. In the example above you find `Lisa` with: ```jsx var lisa = component.findByQuery('div > ul > li > div > h1')[1]; ``` On the flip side, you cannot use `findByQuery` to find your components because, after rendering, they were replaced by the HTML they generate in their `render` method: ```jsx var persons = component.findByQuery('Person'); expect(persons.length).toEqual(0); ``` #### Shallow rendering Sometimes you might want to stop rendering after the first level of components. In true unit test spirit you would like to just test a component assuming the components it depends upon are working. To achieve this you can use `createComponent.shallow` as follows: ```jsx var component = createComponent.shallow( ); ``` And the result would be a representation of the following pseudo-HTML: ```html

          Homer

          ``` To find `Lisa` you could use any of the following: ```jsx var lisaByAttr = component.findByQuery('Person[name=Lisa]')[0]; var lisaByTagAndOrder = component.findByQuery('Person')[1]; var lisaByCompAndOrder = component.findByComponent(Person)[1]; ``` And access the properties as usual: ```jsx expect(lisaByAttr.prop('name')).toEqual('Lisa'); ``` #### Interleaved rendering This rendering mode is similar to the deep mode above with the exception that components are NOT erased form the HTML representation. This means that you can mix and match HTML tags and react components in your `findByQuery` selectors. To use interleaved rendering call `createComponent.interleaved` as follows: ```jsx var component = createComponent.interleaved( ); ``` The result would be a representation of the following pseudo-HTML: ```html

          Homer

          • Bart

            • Lisa

              • Maggie

                ``` And you can find components with: ```jsx var lisaComp = component.findByQuery('Person[name=Lisa]')[0]; var lisaCompAlt = component.findByComponent(Person)[2]; var lisaName = component.findByQuery('Person[name=Lisa] h1')[0]; var lisaNameAlt = lisaComp.findByQuery('h1')[0]; ``` Excluding components -------------------- Using `exclude` you can now leave a component out of test as if it didn't exist. ```jsx //single component createComponent.exclude(ChildComponent)(ParentComponent); createComponent.exclude(ChildComponent).shallow(ParentComponent); createComponent.exclude(ChildComponent).interleaved(ParentComponent); //multi components createComponent.exclude([ChildComponent1, ChildComponent2])(ParentComponent); createComponent.exclude([ChildComponent1, ChildComponent2]).shallow(ParentComponent); createComponent.exclude([ChildComponent1, ChildComponent2]).interleaved(ParentComponent); ``` Mocking components ------------------ Using `mock` you can now replace a component with another ```jsx //single mock createComponent.mock(Actual, Mock)(ParentComponent); //multi mock createComponent .mock(Actual1, Mock1) .mock(Actual2, Mock2)(ParentComponent); ``` Implicit context ---------------- If you want to test components that use React's implicit [context](https://facebook.github.io/react/docs/context.html) you can pass the context to the rendered components by calling `withContext`: ```jsx createComponent.withContext({ key: value})(); ``` More info --------- Note that testing **stateful** components require additional effort. See [test/stateful.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/stateful.jsx) for more details. For more examples on how to test **events** refer to [test/events.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/events.jsx). For more examples on finding elements by **query selectors** refer to [test/find-by-query.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/find-by-query.jsx). For more examples on finding element using a **custom function** refer to [test/find-by.jsx](https://github.com/pzavolinsky/react-unit/blob/master/test/find-by.jsx). ================================================ FILE: dist/add-ons/debug.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var instanceLogger = function (instance) { console.log('[instance]', instance); return instance; }; var componentLogger = function (comp) { console.log('[component]', comp); return comp; }; var resolveLogger = function (comp) { console.log('[resolved]', comp); return comp; }; var debug = function (ctx) { return function () { return ramda_1.merge(ctx, { instanceMapper: ramda_1.compose(instanceLogger, ctx.instanceMapper), componentMapper: ramda_1.compose(componentLogger, ctx.componentMapper), resolveMapper: ramda_1.compose(resolveLogger, ctx.resolveMapper) }); }; }; exports.default = debug; ================================================ FILE: dist/add-ons/exclude.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var utils_1 = require("../utils"); var excludeMapper = function (exclude) { var allowed = exclude.constructor === Array ? function (comp) { return !ramda_1.any(function (t) { return utils_1.isOfType(t, comp); }, exclude); } : function (comp) { return !utils_1.isOfType(exclude, comp); }; return function (comp) { return utils_1.filterChildren(allowed, comp); }; }; var exclude = function (ctx) { return function (exclude) { return ramda_1.merge(ctx, { componentMapper: ramda_1.compose(excludeMapper(exclude), ctx.componentMapper) }); }; }; exports.default = exclude; ================================================ FILE: dist/add-ons/index.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var exclude_1 = require("./exclude"); var mock_1 = require("./mock"); var with_context_1 = require("./with-context"); var debug_1 = require("./debug"); var addOns = { exclude: exclude_1.default, mock: mock_1.default, withContext: with_context_1.default, debug: debug_1.default }; exports.default = addOns; ================================================ FILE: dist/add-ons/mock.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var types_1 = require("../types"); var utils_1 = require("../utils"); var mockMapper = function (actual, mock) { return function (comp) { return types_1.isShallow(comp) && utils_1.isOfType(actual, comp) ? { type: 'shallow', tagName: utils_1.getTagNameForType(mock), instance: ramda_1.merge(comp.instance, { type: mock }) } : comp; }; }; var mock = function (ctx) { return function (actual, mock) { return ramda_1.merge(ctx, { componentMapper: ramda_1.compose(mockMapper(actual, mock), ctx.componentMapper) }); }; }; exports.default = mock; ================================================ FILE: dist/add-ons/with-context.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var withContextMapper = function (context) { return function (instance) { return ramda_1.merge(instance, { context: context }); }; }; var withContext = function (ctx) { return function (context) { return ramda_1.merge(ctx, { instanceMapper: ramda_1.compose(withContextMapper(context), ctx.instanceMapper) }); }; }; exports.default = withContext; ================================================ FILE: dist/internal/test-render-instance.js ================================================ 'use strict'; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _renderInstance = require('../render-instance'); var _renderInstance2 = _interopRequireDefault(_renderInstance); var _types = require('../types'); var React = require('react'); var WithChildren = function WithChildren(_ref) { var child = _ref.child; return React.createElement( 'div', null, child ); }; describe('createComponent', function () { it('should load numeric children', function () { var renderedComponent = (0, _renderInstance2['default'])(React.createElement(WithChildren, { child: 1 })); expect((0, _types.isHtml)(renderedComponent)).toBe(true); var children = renderedComponent.children; expect(children.length).toBe(1); expect((0, _types.isUnknown)(children[0])).toBe(true); }); }); ================================================ FILE: dist/pipeline.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var render_instance_1 = require("./render-instance"); var resolveAndMap = function (ctx, resolver) { var mapAndRender = ramda_1.compose(render_instance_1.default, ctx.instanceMapper); var resolve = resolver(ctx.componentMapper, mapAndRender); return ramda_1.compose( // 4) MAP the ResolvedComponent (optional). ctx.resolveMapper, // 3) RESOLVE the RenderedComponent into ResolvedComponent by applying a // resolve strategy (deep, shallow, interleaved). The result replaces // ShallowReactComponents with either a unknown components or HTML // components. The resolution process applies a ComponentMapper to each // RenderedComponent before resolving. resolve); }; exports.applyRootPipeline = function (ctx, resolver) { return ramda_1.compose(resolveAndMap(ctx, resolver), // 2) RENDER the React INSTANCE into a RenderedComponent (renderInstance) // Note: at this stage, the RenderedComponent tree includes // not-yet-rendered ShallowReactComponents. render_instance_1.default, // 1) MAP the React INSTANCE (optional). ctx.instanceMapper); }; var failShallowArtificial = function (comp) { return function (_) { throw "\n Cannot call renderNew on shallow rendered component " + comp.tagName + ".\n\n Looks like you are trying test using\n shallow render.\n\n Consider using another rendering mode (e.g. interleaved) or refactoring\n the test to do a shallow stateless test of and a shallow\n stateful test of ;\n "; }; }; exports.applyComponentPipeline = function (ctx, resolver) { return function (comp) { return ramda_1.compose(resolveAndMap(ctx, resolver), // 2) RE-RENDER the React INSTANCE into a RenderedComponent (renderNew) // Note: at this stage, the RenderedComponent tree includes // not-yet-rendered ShallowReactComponents. comp.renderNew || failShallowArtificial(comp), // 1) MAP the React INSTANCE (optional). ctx.instanceMapper); }; }; ================================================ FILE: dist/react-unit.js ================================================ "use strict"; var R = require("ramda"); var types_1 = require("./types"); var resolver_1 = require("./resolver"); var pipeline_1 = require("./pipeline"); var wrapper_1 = require("./wrapper"); var add_ons_1 = require("./add-ons"); var createComponent = function (ctx, resolver) { var rootPipeline = pipeline_1.applyRootPipeline(ctx, resolver); var componentPipeline = pipeline_1.applyComponentPipeline(ctx, resolver); function wrap(resolved) { return types_1.isUnknown(resolved) ? resolved.unknown : wrapper_1.default(undefined, resolved, function (c) { return function (i) { return wrap(componentPipeline(c)(i)); }; }); } return R.compose(wrap, rootPipeline); }; var makeCreateComponent = function (ctx) { var fn = createComponent(ctx, resolver_1.deepResolver); fn.shallow = createComponent(ctx, resolver_1.shallowResolver); fn.interleaved = createComponent(ctx, resolver_1.interleavedResolver); fn.ctx = ctx; return fn; }; function applyAddons(fn) { R.toPairs(add_ons_1.default).forEach(function (_a) { var name = _a[0], addOn = _a[1]; fn[name] = R.compose(applyAddons, makeCreateComponent, addOn(fn.ctx)); }); return fn; } ; module.exports = applyAddons(makeCreateComponent(types_1.defaultRenderContext)); ================================================ FILE: dist/render-instance.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var react_1 = require("react"); var ramda_1 = require("ramda"); var ReactShallowRenderer = require("react-test-renderer/shallow"); var types_1 = require("./types"); var utils_1 = require("./utils"); // --- Rendered Component Wrapper Functions --------------------------------- // function wrapShallowReactComponent(renderOutput) { var type = renderOutput.type; var tagName = utils_1.getTagNameForType(type); return { type: 'shallow', tagName: tagName, instance: renderOutput }; } ; var getPropsForOutput = function (_a) { var key = _a.key, ref = _a.ref, props = _a.props, _store = _a._store // tslint:disable-line ; return ramda_1.mergeAll([ _store && _store.props, props, { key: key, ref: ref } ]); }; function wrapHtmlComponent(renderNew, renderOutput, instance) { var type = renderOutput.type, key = renderOutput.key, ref = renderOutput.ref; var props = getPropsForOutput(renderOutput); var children = props.children ? react_1.Children.map(props.children, function (c) { return processRenderOutput(renderNew, c, c); }) : []; return { type: 'html', tagName: type, key: key, ref: ref, props: props, renderOutput: renderOutput, instance: instance, children: children, renderNew: renderNew }; } ; function wrapUnknownComponent(renderOutput) { return { type: 'unknown', unknown: renderOutput }; } function processRenderOutput(renderNew, renderOutput, instance) { if (!renderOutput) { // e.g. render() { return undefined; } return wrapUnknownComponent(renderOutput); } if (typeof renderOutput.type === 'function') { // shallowRender reached another React component and stopped // renderOutput is the spec for that (child) component return wrapShallowReactComponent(renderOutput); } if (renderOutput.type && renderOutput.props) { // shallowRender returned an HTML component return wrapHtmlComponent(renderNew, renderOutput, instance); } return wrapUnknownComponent(renderOutput); } // --- Public API ----------------------------------------------------------- // exports.toArtificialHtml = function (comp, child) { return ({ type: 'artificial', tagName: utils_1.getTagNameForType(comp.instance.type), props: getPropsForOutput(comp.instance), renderOutput: comp.instance, instance: comp.instance, children: child ? [child] : [], renderNew: child && types_1.isHtml(child) ? child.renderNew : undefined }); }; var renderInstance = function (instance) { var shallowRenderer = new ReactShallowRenderer(); function create(componentInstance) { shallowRenderer.render(componentInstance, componentInstance.context); var renderOutput = shallowRenderer.getRenderOutput(); var renderNew = function (newInstance) { return create(newInstance || componentInstance); }; return processRenderOutput(renderNew, renderOutput, componentInstance); } return create(instance); }; exports.default = renderInstance; ================================================ FILE: dist/resolver.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var render_instance_1 = require("./render-instance"); var types_1 = require("./types"); function deepResolver(mapper, renderer) { return function (comp) { var resolve = deepResolver(mapper, renderer); var mapped = mapper(comp); if (types_1.isShallow(mapped)) { var newComp = renderer(mapped.instance); // note that newComp might be shallow again, so we need to call // deepResolver on it to resolve the whole the tree return resolve(newComp); } if (types_1.isHtml(mapped)) return ramda_1.merge(mapped, { children: mapped.children.map(resolve) }); return mapped; }; } exports.deepResolver = deepResolver; function shallowResolver(mapper, _) { return function (comp) { var resolve = shallowResolver(mapper, _); var mapped = mapper(comp); if (types_1.isShallow(mapped)) { return render_instance_1.toArtificialHtml(mapped); } if (types_1.isHtml(mapped)) { return ramda_1.merge(mapped, { children: mapped.children.map(resolve) }); } return mapped; }; } exports.shallowResolver = shallowResolver; function interleavedResolver(mapper, renderer, nonRoot) { return function (comp) { var resolve = interleavedResolver(mapper, renderer, true); var mapped = mapper(comp); if (types_1.isShallow(mapped)) { var newComp = renderer(mapped.instance); // note that newComp might be shallow again, so we need to call // interleavedResolver on it to render the whole the tree var renderedComp = resolve(newComp); return render_instance_1.toArtificialHtml(mapped, renderedComp); } if (types_1.isHtml(mapped)) { var mappedWithChildren = ramda_1.merge(mapped, { children: mapped.children.map(resolve) }); return nonRoot ? mappedWithChildren : render_instance_1.toArtificialHtml(mappedWithChildren, mappedWithChildren); } return mapped; }; } exports.interleavedResolver = interleavedResolver; ================================================ FILE: dist/sizzle-bundle.js ================================================ var document = { createElement: function(tag) { return { appendChild: function() {}, getElementsByTagName: function() { return []; }, getAttribute: function() {} } }, createComment: function(s) { return s; }, getElementsByClassName: ' { [native code] }', nodeType: 9, documentElement: { nodeName: 'HTML' } }; document.ownerDocument = document; var window = { document: document }; var _ = (function() { /*! * Sizzle CSS Selector Engine v2.2.1-pre * http://sizzlejs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2015-08-17 */ (function( window ) { var i, support, Expr, getText, isXML, tokenize, compile, select, outermostContext, sortInput, hasDuplicate, // Local document vars setDocument, document, docElem, documentIsHTML, rbuggyQSA, rbuggyMatches, matches, contains, // Instance-specific data expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; }, // General-purpose constants MAX_NEGATIVE = 1 << 31, // Instance methods hasOwn = ({}).hasOwnProperty, arr = [], pop = arr.pop, push_native = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf as it's faster than native // http://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { if ( list[i] === elem ) { return i; } } return -1; }, booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { "ID": new RegExp( "^#(" + identifier + ")" ), "CLASS": new RegExp( "^\\.(" + identifier + ")" ), "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, rnative = /^[^{]+\{\s*\[native \w/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, rescape = /'|\\/g, // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), funescape = function( _, escaped, escapedWhitespace ) { var high = "0x" + escaped - 0x10000; // NaN means non-codepoint // Support: Firefox<24 // Workaround erroneous numeric interpretation of +"0x" return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint String.fromCharCode( high + 0x10000 ) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }; // Optimize for push.apply( _, NodeList ) try { push.apply( (arr = slice.call( preferredDoc.childNodes )), preferredDoc.childNodes ); // Support: Android<4.0 // Detect silently failing push.apply arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { push_native.apply( target, slice.call(els) ); } : // Support: IE<9 // Otherwise append directly function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length while ( (target[j++] = els[i++]) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; } // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // ID selector if ( (m = match[1]) ) { // Document context if ( nodeType === 9 ) { if ( (elem = context.getElementById( m )) ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } // Element context } else { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( newContext && (elem = newContext.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Type selector } else if ( match[2] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll if ( support.qsa && !compilerCache[ selector + " " ] && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { if ( nodeType !== 1 ) { newContext = context; newSelector = selector; // qSA looks outside Element context, which is not what we want // Thanks to Andrew Dupont for this workaround technique // Support: IE <=8 // Exclude object elements } else if ( context.nodeName.toLowerCase() !== "object" ) { // Capture the context ID, setting it first if necessary if ( (nid = context.getAttribute( "id" )) ) { nid = nid.replace( rescape, "\\CONTENT" ); } else { context.setAttribute( "id", (nid = expando) ); } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { groups[i] = "[id='" + nid + "'] " + toSelector( groups[i] ); } newSelector = groups.join( "," ); // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; } if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch ( qsaError ) { } finally { if ( nid === expando ) { context.removeAttribute( "id" ); } } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); } /** * Create key-value caches of limited size * @returns {function(string, object)} Returns the Object data after storing it on itself with * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) * deleting the oldest entry */ function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } return (cache[ key + " " ] = value); } return cache; } /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } /** * Support testing using an element * @param {Function} fn Passed the created div and expects a boolean result */ function assert( fn ) { var div = document.createElement("div"); try { return !!fn( div ); } catch (e) { return false; } finally { // Remove from its parent by default if ( div.parentNode ) { div.parentNode.removeChild( div ); } // release memory in IE div = null; } } /** * Adds the same handler for all of the specified attrs * @param {String} attrs Pipe-separated list of attributes * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { var arr = attrs.split("|"), i = attrs.length; while ( i-- ) { Expr.attrHandle[ arr[i] ] = handler; } } /** * Checks document order of two siblings * @param {Element} a * @param {Element} b * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b */ function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); // Use IE sourceIndex if available on both nodes if ( diff ) { return diff; } // Check if b follows a if ( cur ) { while ( (cur = cur.nextSibling) ) { if ( cur === b ) { return -1; } } } return a ? 1 : -1; } /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === type; }; } /** * Returns a function to use in pseudos for buttons * @param {String} type */ function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && elem.type === type; }; } /** * Returns a function to use in pseudos for positionals * @param {Function} fn */ function createPositionalPseudo( fn ) { return markFunction(function( argument ) { argument = +argument; return markFunction(function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { if ( seed[ (j = matchIndexes[i]) ] ) { seed[j] = !(matches[j] = seed[j]); } } }); }); } /** * Checks a node for validity as a Sizzle context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience support = Sizzle.support = {}; /** * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = elem && (elem.ownerDocument || elem).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; /** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { var hasCompare, parent, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML( document ); // Support: IE 9-11, Edge // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) if ( (parent = document.defaultView) && parent.top !== parent ) { // Support: IE 11 if ( parent.addEventListener ) { parent.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only } else if ( parent.attachEvent ) { parent.attachEvent( "onunload", unloadHandler ); } } /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert(function( div ) { div.className = "i"; return !div.getAttribute("className"); }); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert(function( div ) { div.appendChild( document.createComment("") ); return !div.getElementsByTagName("*").length; }); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programatically-set names, // so use a roundabout getElementsByName test support.getById = assert(function( div ) { docElem.appendChild( div ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; }); // ID find and filter if ( support.getById ) { Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var m = context.getElementById( id ); return m ? [ m ] : []; } }; Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute("id") === attrId; }; }; } else { // Support: IE6/7 // getElementById is not reliable as a find shortcut delete Expr.find["ID"]; Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return node && node.value === attrId; }; }; } // Tag Expr.find["TAG"] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); // DocumentFragment nodes don't have gEBTN } else if ( support.qsa ) { return context.querySelectorAll( tag ); } } : function( tag, context ) { var elem, tmp = [], i = 0, // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { while ( (elem = results[i++]) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } } return tmp; } return results; }; // Class Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; /* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See http://bugs.jquery.com/ticket/13378 rbuggyQSA = []; if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { // Build QSA regex // Regex strategy adopted from Diego Perini assert(function( div ) { // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // http://bugs.jquery.com/ticket/12359 docElem.appendChild( div ).innerHTML = "" + ""; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section if ( div.querySelectorAll("[msallowcapture^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly if ( !div.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { rbuggyQSA.push("~="); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !div.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibing-combinator selector` fails if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { rbuggyQSA.push(".#.+[+~]"); } }); assert(function( div ) { // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment var input = document.createElement("input"); input.setAttribute( "type", "hidden" ); div.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute if ( div.querySelectorAll("[name=d]").length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests if ( !div.querySelectorAll(":enabled").length ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Opera 10-11 does not throw on post-comma invalid pseudos div.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); } if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector) )) ) { assert(function( div ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( div, "div" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( div, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); }); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully self-exclusive // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // Choose the first element that is related to our preferred document if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { return -1; } if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { return 1; } // Maintain original order return sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison cur = a; while ( (cur = cur.parentNode) ) { ap.unshift( cur ); } cur = b; while ( (cur = cur.parentNode) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[i] === bp[i] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[i], bp[i] ) : // Otherwise nodes in our document sort first ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; }; return document; }; Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); }; Sizzle.matchesSelector = function( elem, expr ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } // Make sure that attribute selectors are quoted expr = expr.replace( rattributeQuotes, "='$1']" ); if ( support.matchesSelector && documentIsHTML && !compilerCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch (e) {} } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { // Set document vars if needed if ( ( context.ownerDocument || context ) !== document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Document sorting and removing duplicates * @param {ArrayLike} results */ Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { while ( (elem = results[i++]) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } // Clear input after sorting to release objects // See https://github.com/jquery/sizzle/pull/225 sortInput = null; return results; }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // If no nodeType, this is expected to be an array while ( (node = elem[i++]) ) { // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } // Do not include comment or processing instruction nodes return ret; }; Expr = Sizzle.selectors = { // Can be adjusted by the user cacheLength: 50, createPseudo: markFunction, match: matchExpr, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }, preFilter: { "ATTR": function( match ) { match[1] = match[1].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); if ( match[2] === "~=" ) { match[3] = " " + match[3] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[1] = match[1].toLowerCase(); if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument if ( !match[3] ) { Sizzle.error( match[0] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); // other types prohibit arguments } else if ( match[3] ) { Sizzle.error( match[0] ); } return match; }, "PSEUDO": function( match ) { var excess, unquoted = !match[6] && match[2]; if ( matchExpr["CHILD"].test( match[0] ) ) { return null; } // Accept quoted arguments as-is if ( match[3] ) { match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && // Get excess from tokenize (recursively) (excess = tokenize( unquoted, true )) && // advance to the next closing parenthesis (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { // excess is a negative index match[0] = match[0].slice( 0, excess ); match[2] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) return match.slice( 0, 3 ); } }, filter: { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; }, "CLASS": function( className ) { var pattern = classCache[ className + " " ]; return pattern || (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && classCache( className, function( elem ) { return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); }); }, "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } result += ""; return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; }; }, "CHILD": function( type, what, argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; return first === 1 && last === 0 ? // Shortcut for :nth-*(n) function( elem ) { return !!elem.parentNode; } : function( elem, context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), useCache = !xml && !ofType, diff = false; if ( parent ) { // :(first|last|only)-(child|of-type) if ( simple ) { while ( dir ) { node = elem; while ( (node = node[ dir ]) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { return false; } } // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } return true; } start = [ forward ? parent.firstChild : parent.lastChild ]; // non-xml :nth-child(...) stores cache data on `parent` if ( forward && useCache ) { // Seek `elem` from a previously-cached index // ...in a gzip-friendly way node = parent; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; while ( (node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start (diff = nodeIndex = 0) || start.pop()) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } } else { // Use previously-cached element index if available if ( useCache ) { // ...in a gzip-friendly way node = elem; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { // Use the same loop as above to seek `elem` from the start while ( (node = ++nodeIndex && node && node[ dir ] || (diff = nodeIndex = 0) || start.pop()) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { // Cache the index of each encountered element if ( useCache ) { outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); uniqueCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { break; } } } } } // Incorporate the offset, then check against cycle size diff -= last; return diff === first || ( diff % first === 0 && diff / first >= 0 ); } }; }, "PSEUDO": function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Sizzle does if ( fn[ expando ] ) { return fn( argument ); } // But maintain support for old signatures if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? markFunction(function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { idx = indexOf( seed, matched[i] ); seed[ idx ] = !( matches[ idx ] = matched[i] ); } }) : function( elem ) { return fn( elem, 0, args ); }; } return fn; } }, pseudos: { // Potentially complex pseudos "not": markFunction(function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? markFunction(function( seed, matches, context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { if ( (elem = unmatched[i]) ) { seed[i] = !(matches[i] = elem); } } }) : function( elem, context, xml ) { input[0] = elem; matcher( input, null, xml, results ); // Don't keep the element (issue #299) input[0] = null; return !results.pop(); }; }), "has": markFunction(function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; }), "contains": markFunction(function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; }; }), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test(lang || "") ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { if ( (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); return false; }; }), // Miscellaneous "target": function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, "root": function( elem ) { return elem === docElem; }, "focus": function( elem ) { return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); }, // Boolean properties "enabled": function( elem ) { return elem.disabled === false; }, "disabled": function( elem ) { return elem.disabled === true; }, "checked": function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); }, "selected": function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, // Contents "empty": function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { if ( elem.nodeType < 6 ) { return false; } } return true; }, "parent": function( elem ) { return !Expr.pseudos["empty"]( elem ); }, // Element/input types "header": function( elem ) { return rheader.test( elem.nodeName ); }, "input": function( elem ) { return rinputs.test( elem.nodeName ); }, "button": function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, "text": function( elem ) { var attr; return elem.nodeName.toLowerCase() === "input" && elem.type === "text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection "first": createPositionalPseudo(function() { return [ 0 ]; }), "last": createPositionalPseudo(function( matchIndexes, length ) { return [ length - 1 ]; }), "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; }), "even": createPositionalPseudo(function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "odd": createPositionalPseudo(function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; }), "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; }) } }; Expr.pseudos["nth"] = Expr.pseudos["eq"]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { Expr.pseudos[ i ] = createInputPseudo( i ); } for ( i in { submit: true, reset: true } ) { Expr.pseudos[ i ] = createButtonPseudo( i ); } // Easy API for creating new setFilters function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); tokenize = Sizzle.tokenize = function( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); } soFar = selector; groups = []; preFilters = Expr.preFilter; while ( soFar ) { // Comma and first run if ( !matched || (match = rcomma.exec( soFar )) ) { if ( match ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[0].length ) || soFar; } groups.push( (tokens = []) ); } matched = false; // Combinators if ( (match = rcombinators.exec( soFar )) ) { matched = match.shift(); tokens.push({ value: matched, // Cast descendant combinators to space type: match[0].replace( rtrim, " " ) }); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || (match = preFilters[ type ]( match ))) ) { matched = match.shift(); tokens.push({ value: matched, type: type, matches: match }); soFar = soFar.slice( matched.length ); } } if ( !matched ) { break; } } // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens return parseOnly ? soFar.length : soFar ? Sizzle.error( selector ) : // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; function toSelector( tokens ) { var i = 0, len = tokens.length, selector = ""; for ( ; i < len; i++ ) { selector += tokens[i].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, checkNonElements = base && dir === "parentNode", doneName = done++; return combinator.first ? // Check against closest ancestor/preceding element function( elem, context, xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } } : // Check against all ancestor/preceding elements function( elem, context, xml ) { var oldCache, uniqueCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; } } } } else { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || (elem[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); if ( (oldCache = uniqueCache[ dir ]) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements return (newCache[ 2 ] = oldCache[ 2 ]); } else { // Reuse newcache so results back-propagate to previous elements uniqueCache[ dir ] = newCache; // A match means we're done; a fail means we have to keep checking if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { return true; } } } } } }; } function elementMatcher( matchers ) { return matchers.length > 1 ? function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { if ( !matchers[i]( elem, context, xml ) ) { return false; } } return true; } : matchers[0]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { Sizzle( selector, contexts[i], results ); } return results; } function condense( unmatched, map, filter, context, xml ) { var elem, newUnmatched = [], i = 0, len = unmatched.length, mapped = map != null; for ( ; i < len; i++ ) { if ( (elem = unmatched[i]) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { map.push( i ); } } } } return newUnmatched; } function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } return markFunction(function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } // Apply postFilter if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( (elem = temp[i]) ) { matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); } } } if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) ) { // Restore matcherIn since elem is not yet a final match temp.push( (matcherIn[i] = elem) ); } } postFinder( null, (matcherOut = []), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) && (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { postFinder( null, results, matcherOut, xml ); } else { push.apply( results, matcherOut ); } } }); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, leadingRelative = Expr.relative[ tokens[0].type ], implicitRelative = leadingRelative || Expr.relative[" "], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) matchContext = addCombinator( function( elem ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( (checkContext = context).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { if ( (matcher = Expr.relative[ tokens[i].type ]) ) { matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; } else { matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[j].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } return elementMatcher( matchers ); } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, matchedCount = 0, i = "0", unmatched = seed && [], setMatched = [], contextBackup = outermostContext, // We must always have either seed elements or outermost context elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), len = elems.length; if ( outermost ) { outermostContext = context === document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id for ( ; i !== len && (elem = elems[i]) != null; i++ ) { if ( byElement && elem ) { j = 0; if ( !context && elem.ownerDocument !== document ) { setDocument( elem ); xml = !documentIsHTML; } while ( (matcher = elementMatchers[j++]) ) { if ( matcher( elem, context || document, xml) ) { results.push( elem ); break; } } if ( outermost ) { dirruns = dirrunsUnique; } } // Track unmatched elements for set filters if ( bySet ) { // They will have gone through all possible matchers if ( (elem = !matcher && elem) ) { matchedCount--; } // Lengthen the array for every element, matched or not if ( seed ) { unmatched.push( elem ); } } } // `i` is now the count of elements visited above, and adding it to `matchedCount` // makes the latter nonnegative. matchedCount += i; // Apply set filters to unmatched elements // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` // equals `i`), unless we didn't visit _any_ elements in the above loop because we have // no element matchers and no seed. // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that // case, which will result in a "00" `matchedCount` that differs from `i` but is also // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; while ( (matcher = setMatchers[j++]) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { if ( !(unmatched[i] || setMatched[i]) ) { setMatched[i] = pop.call( results ); } } } // Discard index placeholder values to get only actual matches setMatched = condense( setMatched ); } // Add matches to results push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { Sizzle.uniqueSort( results ); } } // Override manipulation of globals by nested matchers if ( outermost ) { dirruns = dirrunsUnique; outermostContext = contextBackup; } return unmatched; }; return bySet ? markFunction( superMatcher ) : superMatcher; } compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { cached = matcherFromTokens( match[i] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { elementMatchers.push( cached ); } } // Cache the compiled function cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; }; /** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( (selector = compiled.selector || selector) ); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees us context) if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID tokens = match[0] = match[0].slice( 0 ); if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && support.getById && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; if ( !context ) { return results; // Precompiled matchers will still verify ancestry, so step up a level } else if ( compiled ) { context = context.parentNode; } selector = selector.slice( tokens.shift().value.length ); } // Fetch a seed set for right-to-left matching i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[i]; // Abort if we hit a combinator if ( Expr.relative[ (type = token.type) ] ) { break; } if ( (find = Expr.find[ type ]) ) { // Search, expanding context for leading sibling combinators if ( (seed = find( token.matches[0].replace( runescape, funescape ), rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context )) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above ( compiled || compile( selector, match ) )( seed, context, !documentIsHTML, results, !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }; // One-time assignments // Sort stability support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* support.sortDetached = assert(function( div1 ) { // Should return 1, but returns 4 (following) return div1.compareDocumentPosition( document.createElement("div") ) & 1; }); // Support: IE<8 // Prevent attribute/property "interpolation" // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !assert(function( div ) { div.innerHTML = ""; return div.firstChild.getAttribute("href") === "#" ; }) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } }); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert(function( div ) { div.innerHTML = ""; div.firstChild.setAttribute( "value", "" ); return div.firstChild.getAttribute( "value" ) === ""; }) ) { addHandle( "value", function( elem, name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } }); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies if ( !assert(function( div ) { return div.getAttribute("disabled") == null; }) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : (val = elem.getAttributeNode( name )) && val.specified ? val.value : null; } }); } // EXPOSE if ( typeof define === "function" && define.amd ) { define(function() { return Sizzle; }); // Sizzle requires that there be a global window in Common-JS like environments } else if ( typeof module !== "undefined" && module.exports ) { module.exports = Sizzle; } else { window.Sizzle = Sizzle; } // EXPOSE })( window ); ; })(window); ================================================ FILE: dist/types.js ================================================ "use strict"; // The goal of react-unit is to process react `instances` and turn them into // `UnitComponents`. So, when all is said and done,react-unit looks like: // // function createComponent(instance:ReactInstance):UnitComponent; // // UnitComponent is a bit complex and deserves a whole file, see // `unit-component.ts`. Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultRenderContext = ({ instanceMapper: function (i) { return i; }, componentMapper: function (c) { return c; }, resolveMapper: function (r) { return r; } }); // === Type guards ========================================================== // exports.isShallow = function (c) { return c.type === 'shallow'; }; exports.isHtml = function (c) { return c.type === 'html' || c.type === 'artificial'; }; exports.isArtificialHtml = function (c) { return c.type === 'artificial'; }; exports.isUnknown = function (c) { return c.type === 'unknown'; }; ================================================ FILE: dist/unit-component.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var R = require("ramda"); var types_1 = require("./types"); var utils_1 = require("./utils"); var sizzle = require("./sizzle-bundle"); var ROOT = 'root'; var isNotRoot = function (c) { return c !== ROOT; }; // Component wrapper var UnitComponent = /** @class */ (function () { function UnitComponent(comp, parent, renderNew) { this.nodeType = 1; this.type = comp.tagName; this.key = comp.key; this.ref = comp.ref; this.props = comp.props; this.texts = []; this.comp = comp; this.renderNew = renderNew; // Mock root parent (to enable top-level "[attr=value]") if (!parent) { this.root = new UnitComponent({}, ROOT, undefined); this.root.props = { children: this }; this.parentNode = this.root; } else if (isNotRoot(parent)) { this.parentNode = parent; } // Mock DOM this.ownerDocument = global.document; this.nodeName = this.type; } UnitComponent.prototype.prop = function (name) { return (this.props || {})[name]; }; UnitComponent.prototype.findBy = function (fn) { var ret = []; if (fn(this)) ret.push(this); var children = this.prop('children'); if (!children || utils_1.isText(children)) return ret; if (children.length === undefined) children = [children]; return R.compose(R.concat(ret), R.filter(function (c) { return !!c; }), R.flatten, R.map(function (c) { return c.findBy && c.findBy(fn); }))(children); }; UnitComponent.prototype.findByRef = function (ref) { return this.findBy(function (c) { return c.ref == ref; }); }; UnitComponent.prototype.findByTag = function (type) { return this.findBy(type == '*' ? (function (c) { return true; }) : (function (c) { return c.type == type; })); }; UnitComponent.prototype.findByClassName = function (search) { var pattern = new RegExp('(^|\\s)' + search + '(\\s|$)'); return this.findBy(function (e) { return pattern.test(e.prop('className')); }); }; UnitComponent.prototype.findByComponent = function (componentClass) { return this.findBy(function (e) { return types_1.isArtificialHtml(e.comp) && utils_1.isOfType(componentClass, e.comp); }); }; UnitComponent.prototype.on = function (event, e) { event = "on" + (event[0].toUpperCase() + event.slice(1)); var handler = this.props[event]; if (!handler) throw "Triggered unhandled " + event + " event: " + new Error().stack; return handler(e); }; UnitComponent.prototype.onChange = function (e) { this.on('change', e); }; UnitComponent.prototype.onClick = function (e) { this.on('click', e); }; UnitComponent.prototype.setValueKey = function (n, v) { this.onChange({ target: R.merge(this.props, (_a = {}, _a[n] = v, _a)) }); var _a; }; UnitComponent.prototype.setValue = function (v) { this.setValueKey('value', v); }; UnitComponent.prototype.setChecked = function (v) { this.setValueKey('checked', v); }; UnitComponent.prototype.findByQuery = function (s) { try { return sizzle(s, this.root || this); } catch (e) { console.log('Sizzle error', e.stack); throw e; } }; UnitComponent.prototype.dump = function (padd) { if (!padd) padd = ''; var children = this.prop('children'); var tag = this.type + R.compose(R.join(''), R.map(function (_a) { var k = _a[0], v = _a[1]; return " " + k + "='" + v + "'"; }), R.filter(function (_a) { var _ = _a[0], v = _a[1]; return utils_1.isText(v) && (!!v || v === 0); }), R.toPairs, R.merge({ key: this.key, ref: this.ref }), R.omit(['children']))(this.props); if (!children || children.length === 0) { return this.text ? padd + "<" + tag + ">" + this.text + "\n" : padd + "<" + tag + " />\n"; } if (utils_1.isText(children)) { return padd + "<" + tag + ">" + children + "\n"; } if (children.length === undefined) children = [children]; var texts = children .map(function (c) { return c.dump(padd + ' '); }) .join(''); return padd + "<" + tag + ">\n" + texts + padd + "\n"; }; Object.defineProperty(UnitComponent.prototype, "children", { get: function () { return this.props.children; }, enumerable: true, configurable: true }); Object.defineProperty(UnitComponent.prototype, "textContent", { get: function () { return this.text; }, enumerable: true, configurable: true }); UnitComponent.prototype.getElementsByTagName = function (tagName) { return this.findByTag(tagName); }; UnitComponent.prototype.getElementsByClassName = function (className) { return this.findByClassName(className); }; ; UnitComponent.prototype.getAttribute = function (name) { return this.prop(name == 'class' ? 'className' : name); }; UnitComponent.prototype.getAttributeNode = function (name) { var prop = this.prop(name); return { value: prop, specified: prop !== undefined }; }; ; return UnitComponent; }()); exports.UnitComponent = UnitComponent; ================================================ FILE: dist/utils.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var test_utils_1 = require("react-dom/test-utils"); var types_1 = require("./types"); var notText = ['object', 'function']; exports.isText = function (v) { return !ramda_1.contains(typeof v, notText); }; exports.isOfType = function (type, comp) { return (types_1.isHtml(comp) || types_1.isShallow(comp)) && test_utils_1.isElementOfType(comp.instance, type); }; exports.getTagNameForType = function (type) { return type.displayName || type.name || 'anonymous-component'; }; function filterChildren(fn, comp) { return types_1.isHtml(comp) ? ramda_1.merge(comp, { children: comp.children.filter(fn).map(function (c) { return filterChildren(fn, c); }) }) : comp; } exports.filterChildren = filterChildren; ================================================ FILE: dist/wrapper.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var types_1 = require("./types"); var unit_component_1 = require("./unit-component"); function wrapRenderedIntoUnitComponent(parent, htmlComponent, componentPipeline) { var unitComponent = new unit_component_1.UnitComponent(htmlComponent, parent, componentPipeline(htmlComponent)); var children = []; var texts = []; htmlComponent.children.forEach(function (c) { if (types_1.isUnknown(c)) { var unknown = c.unknown; if (unknown !== undefined && unknown !== null) { texts.push(unknown.toString()); } } else if (types_1.isHtml(c)) { var child = wrapRenderedIntoUnitComponent(unitComponent, c, componentPipeline); children.push(child); texts = texts.concat(child.texts); } // we ignore NotRenderedReactComponents }); unitComponent.props.children = children; unitComponent.texts = texts; unitComponent.text = texts.join(''); return unitComponent; } exports.default = wrapRenderedIntoUnitComponent; ================================================ FILE: gulpfile.js ================================================ "use strict" var gulp = require('gulp'); var babel = require('gulp-babel'); var insert = require('gulp-insert'); var rename = require('gulp-rename'); var jasmine = require('gulp-jasmine'); var runSequence = require('run-sequence'); var fs = require('fs'); var path = require('path'); var del = require('del'); var ts = require("gulp-typescript"); gulp.task('default', ['test']); gulp.task('clean', function(cb) { del('./dist', cb); }); gulp.task('build', function(cb) { runSequence( 'clean', ['build-sizzle', 'build-ts'], cb ); }); gulp.task('build-ts', function() { var tsconfig = JSON.parse( fs.readFileSync(path.join(__dirname, 'tsconfig.json')) ); var opts = tsconfig.compilerOptions; opts.typescript = require('typescript'); return gulp.src(tsconfig.filesGlob) .pipe(ts(opts)) .pipe(gulp.dest("./dist")); }); gulp.task('build-sizzle', function () { var content = fs.readFileSync('./lib/sizzle.js'); return gulp.src('./lib/sizzle-bundle.js') .pipe(insert.transform(function(s) {return s.replace('CONTENT', content)})) .pipe(gulp.dest('./src')) .pipe(gulp.dest('./dist')); }); gulp.task('build-test', ['build'], function() { return gulp.src('./test/**/*.jsx') .pipe(babel()) .pipe(rename({prefix: 'test-'})) .pipe(gulp.dest('./dist')); }); gulp.task('test', ['build-test'], function () { return gulp.src('dist/**/test-*.js') .pipe(jasmine({verbose: true, includeStackTrace: true})); }); ================================================ FILE: lib/sizzle-bundle.js ================================================ var document = { createElement: function(tag) { return { appendChild: function() {}, getElementsByTagName: function() { return []; }, getAttribute: function() {} } }, createComment: function(s) { return s; }, getElementsByClassName: ' { [native code] }', nodeType: 9, documentElement: { nodeName: 'HTML' } }; document.ownerDocument = document; var window = { document: document }; var _ = (function() { CONTENT; })(window); ================================================ FILE: lib/sizzle.js ================================================ /*! * Sizzle CSS Selector Engine v2.2.1-pre * http://sizzlejs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2015-08-17 */ (function( window ) { var i, support, Expr, getText, isXML, tokenize, compile, select, outermostContext, sortInput, hasDuplicate, // Local document vars setDocument, document, docElem, documentIsHTML, rbuggyQSA, rbuggyMatches, matches, contains, // Instance-specific data expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; }, // General-purpose constants MAX_NEGATIVE = 1 << 31, // Instance methods hasOwn = ({}).hasOwnProperty, arr = [], pop = arr.pop, push_native = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf as it's faster than native // http://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { if ( list[i] === elem ) { return i; } } return -1; }, booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { "ID": new RegExp( "^#(" + identifier + ")" ), "CLASS": new RegExp( "^\\.(" + identifier + ")" ), "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, rnative = /^[^{]+\{\s*\[native \w/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, rescape = /'|\\/g, // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), funescape = function( _, escaped, escapedWhitespace ) { var high = "0x" + escaped - 0x10000; // NaN means non-codepoint // Support: Firefox<24 // Workaround erroneous numeric interpretation of +"0x" return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint String.fromCharCode( high + 0x10000 ) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }; // Optimize for push.apply( _, NodeList ) try { push.apply( (arr = slice.call( preferredDoc.childNodes )), preferredDoc.childNodes ); // Support: Android<4.0 // Detect silently failing push.apply arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { push_native.apply( target, slice.call(els) ); } : // Support: IE<9 // Otherwise append directly function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length while ( (target[j++] = els[i++]) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; } // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // ID selector if ( (m = match[1]) ) { // Document context if ( nodeType === 9 ) { if ( (elem = context.getElementById( m )) ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } // Element context } else { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( newContext && (elem = newContext.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Type selector } else if ( match[2] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll if ( support.qsa && !compilerCache[ selector + " " ] && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { if ( nodeType !== 1 ) { newContext = context; newSelector = selector; // qSA looks outside Element context, which is not what we want // Thanks to Andrew Dupont for this workaround technique // Support: IE <=8 // Exclude object elements } else if ( context.nodeName.toLowerCase() !== "object" ) { // Capture the context ID, setting it first if necessary if ( (nid = context.getAttribute( "id" )) ) { nid = nid.replace( rescape, "\\$&" ); } else { context.setAttribute( "id", (nid = expando) ); } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { groups[i] = "[id='" + nid + "'] " + toSelector( groups[i] ); } newSelector = groups.join( "," ); // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; } if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch ( qsaError ) { } finally { if ( nid === expando ) { context.removeAttribute( "id" ); } } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); } /** * Create key-value caches of limited size * @returns {function(string, object)} Returns the Object data after storing it on itself with * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) * deleting the oldest entry */ function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } return (cache[ key + " " ] = value); } return cache; } /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } /** * Support testing using an element * @param {Function} fn Passed the created div and expects a boolean result */ function assert( fn ) { var div = document.createElement("div"); try { return !!fn( div ); } catch (e) { return false; } finally { // Remove from its parent by default if ( div.parentNode ) { div.parentNode.removeChild( div ); } // release memory in IE div = null; } } /** * Adds the same handler for all of the specified attrs * @param {String} attrs Pipe-separated list of attributes * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { var arr = attrs.split("|"), i = attrs.length; while ( i-- ) { Expr.attrHandle[ arr[i] ] = handler; } } /** * Checks document order of two siblings * @param {Element} a * @param {Element} b * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b */ function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); // Use IE sourceIndex if available on both nodes if ( diff ) { return diff; } // Check if b follows a if ( cur ) { while ( (cur = cur.nextSibling) ) { if ( cur === b ) { return -1; } } } return a ? 1 : -1; } /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === type; }; } /** * Returns a function to use in pseudos for buttons * @param {String} type */ function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && elem.type === type; }; } /** * Returns a function to use in pseudos for positionals * @param {Function} fn */ function createPositionalPseudo( fn ) { return markFunction(function( argument ) { argument = +argument; return markFunction(function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { if ( seed[ (j = matchIndexes[i]) ] ) { seed[j] = !(matches[j] = seed[j]); } } }); }); } /** * Checks a node for validity as a Sizzle context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience support = Sizzle.support = {}; /** * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = elem && (elem.ownerDocument || elem).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; /** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { var hasCompare, parent, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML( document ); // Support: IE 9-11, Edge // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) if ( (parent = document.defaultView) && parent.top !== parent ) { // Support: IE 11 if ( parent.addEventListener ) { parent.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only } else if ( parent.attachEvent ) { parent.attachEvent( "onunload", unloadHandler ); } } /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert(function( div ) { div.className = "i"; return !div.getAttribute("className"); }); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert(function( div ) { div.appendChild( document.createComment("") ); return !div.getElementsByTagName("*").length; }); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programatically-set names, // so use a roundabout getElementsByName test support.getById = assert(function( div ) { docElem.appendChild( div ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; }); // ID find and filter if ( support.getById ) { Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var m = context.getElementById( id ); return m ? [ m ] : []; } }; Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute("id") === attrId; }; }; } else { // Support: IE6/7 // getElementById is not reliable as a find shortcut delete Expr.find["ID"]; Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return node && node.value === attrId; }; }; } // Tag Expr.find["TAG"] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); // DocumentFragment nodes don't have gEBTN } else if ( support.qsa ) { return context.querySelectorAll( tag ); } } : function( tag, context ) { var elem, tmp = [], i = 0, // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { while ( (elem = results[i++]) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } } return tmp; } return results; }; // Class Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; /* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See http://bugs.jquery.com/ticket/13378 rbuggyQSA = []; if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { // Build QSA regex // Regex strategy adopted from Diego Perini assert(function( div ) { // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // http://bugs.jquery.com/ticket/12359 docElem.appendChild( div ).innerHTML = "" + ""; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section if ( div.querySelectorAll("[msallowcapture^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly if ( !div.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { rbuggyQSA.push("~="); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !div.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibing-combinator selector` fails if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { rbuggyQSA.push(".#.+[+~]"); } }); assert(function( div ) { // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment var input = document.createElement("input"); input.setAttribute( "type", "hidden" ); div.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute if ( div.querySelectorAll("[name=d]").length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests if ( !div.querySelectorAll(":enabled").length ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Opera 10-11 does not throw on post-comma invalid pseudos div.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); } if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector) )) ) { assert(function( div ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( div, "div" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( div, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); }); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully self-exclusive // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // Choose the first element that is related to our preferred document if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { return -1; } if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { return 1; } // Maintain original order return sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison cur = a; while ( (cur = cur.parentNode) ) { ap.unshift( cur ); } cur = b; while ( (cur = cur.parentNode) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[i] === bp[i] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[i], bp[i] ) : // Otherwise nodes in our document sort first ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; }; return document; }; Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); }; Sizzle.matchesSelector = function( elem, expr ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } // Make sure that attribute selectors are quoted expr = expr.replace( rattributeQuotes, "='$1']" ); if ( support.matchesSelector && documentIsHTML && !compilerCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch (e) {} } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { // Set document vars if needed if ( ( context.ownerDocument || context ) !== document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Document sorting and removing duplicates * @param {ArrayLike} results */ Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { while ( (elem = results[i++]) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } // Clear input after sorting to release objects // See https://github.com/jquery/sizzle/pull/225 sortInput = null; return results; }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // If no nodeType, this is expected to be an array while ( (node = elem[i++]) ) { // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } // Do not include comment or processing instruction nodes return ret; }; Expr = Sizzle.selectors = { // Can be adjusted by the user cacheLength: 50, createPseudo: markFunction, match: matchExpr, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }, preFilter: { "ATTR": function( match ) { match[1] = match[1].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); if ( match[2] === "~=" ) { match[3] = " " + match[3] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[1] = match[1].toLowerCase(); if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument if ( !match[3] ) { Sizzle.error( match[0] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); // other types prohibit arguments } else if ( match[3] ) { Sizzle.error( match[0] ); } return match; }, "PSEUDO": function( match ) { var excess, unquoted = !match[6] && match[2]; if ( matchExpr["CHILD"].test( match[0] ) ) { return null; } // Accept quoted arguments as-is if ( match[3] ) { match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && // Get excess from tokenize (recursively) (excess = tokenize( unquoted, true )) && // advance to the next closing parenthesis (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { // excess is a negative index match[0] = match[0].slice( 0, excess ); match[2] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) return match.slice( 0, 3 ); } }, filter: { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; }, "CLASS": function( className ) { var pattern = classCache[ className + " " ]; return pattern || (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && classCache( className, function( elem ) { return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); }); }, "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } result += ""; return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; }; }, "CHILD": function( type, what, argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; return first === 1 && last === 0 ? // Shortcut for :nth-*(n) function( elem ) { return !!elem.parentNode; } : function( elem, context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), useCache = !xml && !ofType, diff = false; if ( parent ) { // :(first|last|only)-(child|of-type) if ( simple ) { while ( dir ) { node = elem; while ( (node = node[ dir ]) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { return false; } } // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } return true; } start = [ forward ? parent.firstChild : parent.lastChild ]; // non-xml :nth-child(...) stores cache data on `parent` if ( forward && useCache ) { // Seek `elem` from a previously-cached index // ...in a gzip-friendly way node = parent; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; while ( (node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start (diff = nodeIndex = 0) || start.pop()) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } } else { // Use previously-cached element index if available if ( useCache ) { // ...in a gzip-friendly way node = elem; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { // Use the same loop as above to seek `elem` from the start while ( (node = ++nodeIndex && node && node[ dir ] || (diff = nodeIndex = 0) || start.pop()) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { // Cache the index of each encountered element if ( useCache ) { outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); uniqueCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { break; } } } } } // Incorporate the offset, then check against cycle size diff -= last; return diff === first || ( diff % first === 0 && diff / first >= 0 ); } }; }, "PSEUDO": function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Sizzle does if ( fn[ expando ] ) { return fn( argument ); } // But maintain support for old signatures if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? markFunction(function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { idx = indexOf( seed, matched[i] ); seed[ idx ] = !( matches[ idx ] = matched[i] ); } }) : function( elem ) { return fn( elem, 0, args ); }; } return fn; } }, pseudos: { // Potentially complex pseudos "not": markFunction(function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? markFunction(function( seed, matches, context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { if ( (elem = unmatched[i]) ) { seed[i] = !(matches[i] = elem); } } }) : function( elem, context, xml ) { input[0] = elem; matcher( input, null, xml, results ); // Don't keep the element (issue #299) input[0] = null; return !results.pop(); }; }), "has": markFunction(function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; }), "contains": markFunction(function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; }; }), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test(lang || "") ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { if ( (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); return false; }; }), // Miscellaneous "target": function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, "root": function( elem ) { return elem === docElem; }, "focus": function( elem ) { return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); }, // Boolean properties "enabled": function( elem ) { return elem.disabled === false; }, "disabled": function( elem ) { return elem.disabled === true; }, "checked": function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); }, "selected": function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, // Contents "empty": function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { if ( elem.nodeType < 6 ) { return false; } } return true; }, "parent": function( elem ) { return !Expr.pseudos["empty"]( elem ); }, // Element/input types "header": function( elem ) { return rheader.test( elem.nodeName ); }, "input": function( elem ) { return rinputs.test( elem.nodeName ); }, "button": function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, "text": function( elem ) { var attr; return elem.nodeName.toLowerCase() === "input" && elem.type === "text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection "first": createPositionalPseudo(function() { return [ 0 ]; }), "last": createPositionalPseudo(function( matchIndexes, length ) { return [ length - 1 ]; }), "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; }), "even": createPositionalPseudo(function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "odd": createPositionalPseudo(function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; }), "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; }) } }; Expr.pseudos["nth"] = Expr.pseudos["eq"]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { Expr.pseudos[ i ] = createInputPseudo( i ); } for ( i in { submit: true, reset: true } ) { Expr.pseudos[ i ] = createButtonPseudo( i ); } // Easy API for creating new setFilters function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); tokenize = Sizzle.tokenize = function( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); } soFar = selector; groups = []; preFilters = Expr.preFilter; while ( soFar ) { // Comma and first run if ( !matched || (match = rcomma.exec( soFar )) ) { if ( match ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[0].length ) || soFar; } groups.push( (tokens = []) ); } matched = false; // Combinators if ( (match = rcombinators.exec( soFar )) ) { matched = match.shift(); tokens.push({ value: matched, // Cast descendant combinators to space type: match[0].replace( rtrim, " " ) }); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || (match = preFilters[ type ]( match ))) ) { matched = match.shift(); tokens.push({ value: matched, type: type, matches: match }); soFar = soFar.slice( matched.length ); } } if ( !matched ) { break; } } // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens return parseOnly ? soFar.length : soFar ? Sizzle.error( selector ) : // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; function toSelector( tokens ) { var i = 0, len = tokens.length, selector = ""; for ( ; i < len; i++ ) { selector += tokens[i].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, checkNonElements = base && dir === "parentNode", doneName = done++; return combinator.first ? // Check against closest ancestor/preceding element function( elem, context, xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } } : // Check against all ancestor/preceding elements function( elem, context, xml ) { var oldCache, uniqueCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; } } } } else { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || (elem[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); if ( (oldCache = uniqueCache[ dir ]) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements return (newCache[ 2 ] = oldCache[ 2 ]); } else { // Reuse newcache so results back-propagate to previous elements uniqueCache[ dir ] = newCache; // A match means we're done; a fail means we have to keep checking if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { return true; } } } } } }; } function elementMatcher( matchers ) { return matchers.length > 1 ? function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { if ( !matchers[i]( elem, context, xml ) ) { return false; } } return true; } : matchers[0]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { Sizzle( selector, contexts[i], results ); } return results; } function condense( unmatched, map, filter, context, xml ) { var elem, newUnmatched = [], i = 0, len = unmatched.length, mapped = map != null; for ( ; i < len; i++ ) { if ( (elem = unmatched[i]) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { map.push( i ); } } } } return newUnmatched; } function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } return markFunction(function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } // Apply postFilter if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( (elem = temp[i]) ) { matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); } } } if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) ) { // Restore matcherIn since elem is not yet a final match temp.push( (matcherIn[i] = elem) ); } } postFinder( null, (matcherOut = []), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) && (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { postFinder( null, results, matcherOut, xml ); } else { push.apply( results, matcherOut ); } } }); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, leadingRelative = Expr.relative[ tokens[0].type ], implicitRelative = leadingRelative || Expr.relative[" "], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) matchContext = addCombinator( function( elem ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( (checkContext = context).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { if ( (matcher = Expr.relative[ tokens[i].type ]) ) { matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; } else { matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[j].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } return elementMatcher( matchers ); } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, matchedCount = 0, i = "0", unmatched = seed && [], setMatched = [], contextBackup = outermostContext, // We must always have either seed elements or outermost context elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), len = elems.length; if ( outermost ) { outermostContext = context === document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id for ( ; i !== len && (elem = elems[i]) != null; i++ ) { if ( byElement && elem ) { j = 0; if ( !context && elem.ownerDocument !== document ) { setDocument( elem ); xml = !documentIsHTML; } while ( (matcher = elementMatchers[j++]) ) { if ( matcher( elem, context || document, xml) ) { results.push( elem ); break; } } if ( outermost ) { dirruns = dirrunsUnique; } } // Track unmatched elements for set filters if ( bySet ) { // They will have gone through all possible matchers if ( (elem = !matcher && elem) ) { matchedCount--; } // Lengthen the array for every element, matched or not if ( seed ) { unmatched.push( elem ); } } } // `i` is now the count of elements visited above, and adding it to `matchedCount` // makes the latter nonnegative. matchedCount += i; // Apply set filters to unmatched elements // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` // equals `i`), unless we didn't visit _any_ elements in the above loop because we have // no element matchers and no seed. // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that // case, which will result in a "00" `matchedCount` that differs from `i` but is also // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; while ( (matcher = setMatchers[j++]) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { if ( !(unmatched[i] || setMatched[i]) ) { setMatched[i] = pop.call( results ); } } } // Discard index placeholder values to get only actual matches setMatched = condense( setMatched ); } // Add matches to results push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { Sizzle.uniqueSort( results ); } } // Override manipulation of globals by nested matchers if ( outermost ) { dirruns = dirrunsUnique; outermostContext = contextBackup; } return unmatched; }; return bySet ? markFunction( superMatcher ) : superMatcher; } compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { cached = matcherFromTokens( match[i] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { elementMatchers.push( cached ); } } // Cache the compiled function cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; }; /** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( (selector = compiled.selector || selector) ); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees us context) if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID tokens = match[0] = match[0].slice( 0 ); if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && support.getById && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; if ( !context ) { return results; // Precompiled matchers will still verify ancestry, so step up a level } else if ( compiled ) { context = context.parentNode; } selector = selector.slice( tokens.shift().value.length ); } // Fetch a seed set for right-to-left matching i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[i]; // Abort if we hit a combinator if ( Expr.relative[ (type = token.type) ] ) { break; } if ( (find = Expr.find[ type ]) ) { // Search, expanding context for leading sibling combinators if ( (seed = find( token.matches[0].replace( runescape, funescape ), rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context )) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above ( compiled || compile( selector, match ) )( seed, context, !documentIsHTML, results, !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }; // One-time assignments // Sort stability support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* support.sortDetached = assert(function( div1 ) { // Should return 1, but returns 4 (following) return div1.compareDocumentPosition( document.createElement("div") ) & 1; }); // Support: IE<8 // Prevent attribute/property "interpolation" // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !assert(function( div ) { div.innerHTML = ""; return div.firstChild.getAttribute("href") === "#" ; }) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } }); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert(function( div ) { div.innerHTML = ""; div.firstChild.setAttribute( "value", "" ); return div.firstChild.getAttribute( "value" ) === ""; }) ) { addHandle( "value", function( elem, name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } }); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies if ( !assert(function( div ) { return div.getAttribute("disabled") == null; }) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : (val = elem.getAttributeNode( name )) && val.specified ? val.value : null; } }); } // EXPOSE if ( typeof define === "function" && define.amd ) { define(function() { return Sizzle; }); // Sizzle requires that there be a global window in Common-JS like environments } else if ( typeof module !== "undefined" && module.exports ) { module.exports = Sizzle; } else { window.Sizzle = Sizzle; } // EXPOSE })( window ); ================================================ FILE: package.json ================================================ { "name": "react-unit", "version": "3.0.3", "description": "Lightweight unit test library for ReactJS.", "main": "dist/react-unit.js", "scripts": { "test": "node_modules/.bin/gulp test" }, "repository": { "type": "git", "url": "https://github.com/pzavolinsky/react-unit.git" }, "keywords": [ "react-component", "unit test", "test", "react", "jasmine" ], "author": "Patricio Zavolinsky", "license": "MIT", "bugs": { "url": "https://github.com/pzavolinsky/react-unit/issues" }, "homepage": "https://github.com/pzavolinsky/react-unit", "devDependencies": { "@types/ramda": "0.0.5", "@types/react-dom": "^0.14.23", "@types/react-test-renderer": "^16.0.1", "del": "^1.2.1", "gulp": "^3.9.0", "gulp-babel": "^5.2.1", "gulp-insert": "^0.5.0", "gulp-jasmine": "^2.0.1", "gulp-rename": "^1.2.2", "gulp-typescript": "^2.13.6", "prop-types": "^15.5.6", "run-sequence": "^1.1.2", "tslint": "^5.0.0", "typescript": "^2.2.2" }, "dependencies": { "ramda": "^0.17.1", "react": ">=15.5.3 <17.0.0", "react-dom": ">=15.5.3 <17.0.0", "react-test-renderer": ">=15.5.3 <17.0.0" } } ================================================ FILE: src/add-ons/debug.ts ================================================ import { compose, merge } from 'ramda'; import { AddOn , InstanceMapper , ComponentMapper , ResolvedMapper , RenderContext } from '../types'; const instanceLogger:InstanceMapper = instance => { console.log('[instance]', instance); return instance; }; const componentLogger:ComponentMapper = comp => { console.log('[component]', comp); return comp; }; const resolveLogger:ResolvedMapper = comp => { console.log('[resolved]', comp); return comp; }; const debug:AddOn = (ctx:RenderContext) => ():RenderContext => merge(ctx, { instanceMapper: compose(instanceLogger, ctx.instanceMapper), componentMapper: compose(componentLogger, ctx.componentMapper), resolveMapper: compose(resolveLogger, ctx.resolveMapper) }); export default debug; ================================================ FILE: src/add-ons/exclude.ts ================================================ import { compose, merge, any } from 'ramda'; import { AddOn , RenderedComponent , RenderContext , ComponentMapper } from '../types'; import { isOfType, filterChildren } from '../utils'; const excludeMapper = (exclude:any[]|any):ComponentMapper => { const allowed:(comp:RenderedComponent) => boolean = exclude.constructor === Array ? comp => !any(t => isOfType(t, comp), exclude) : comp => !isOfType(exclude, comp); return comp => filterChildren(allowed, comp); }; const exclude:AddOn = (ctx:RenderContext) => (exclude:any[]|any):RenderContext => merge(ctx, { componentMapper: compose(excludeMapper(exclude), ctx.componentMapper) }); export default exclude; ================================================ FILE: src/add-ons/index.ts ================================================ import { AddOn } from '../types'; import exclude from './exclude'; import mock from './mock'; import withContext from './with-context'; import debug from './debug'; interface AddOnMap { [name:string]:AddOn } const addOns:AddOnMap = { exclude, mock, withContext, debug }; export default addOns; ================================================ FILE: src/add-ons/mock.ts ================================================ import { compose, merge } from 'ramda'; import { AddOn , ComponentMapper , RenderContext , RenderedComponent , isShallow } from '../types'; import { isOfType, getTagNameForType } from '../utils'; const mockMapper = (actual:any, mock:any):ComponentMapper => (comp:RenderedComponent) => isShallow(comp) && isOfType(actual, comp) ? { type: 'shallow' , tagName: getTagNameForType(mock) , instance: merge(comp.instance, { type: mock }) } : comp; const mock:AddOn = (ctx:RenderContext) => (actual:any, mock:any):RenderContext => merge(ctx, { componentMapper: compose(mockMapper(actual, mock), ctx.componentMapper) }); export default mock; ================================================ FILE: src/add-ons/with-context.ts ================================================ import { compose, merge } from 'ramda'; import { AddOn, InstanceMapper, RenderContext } from '../types'; const withContextMapper = (context:any):InstanceMapper => instance => merge(instance, { context }); const withContext:AddOn = (ctx:RenderContext) => (context:any):RenderContext => merge(ctx, { instanceMapper: compose(withContextMapper(context), ctx.instanceMapper) }); export default withContext; ================================================ FILE: src/defs.d.ts ================================================ declare module "react-test-renderer/shallow" { class ReactShallowRenderer { render(instance:any, ctx:any) : void; getRenderOutput() : any; } export = ReactShallowRenderer; } declare module "react-dom/test-utils" { export function isElementOfType(instance:any, type:any) : boolean; } interface Array { filter(pred:(a:T) => a is U):U[]; } ================================================ FILE: src/pipeline.ts ================================================ import { compose } from 'ramda'; import { RenderContext , Resolver , HtmlComponent , ReactInstance } from './types'; import renderInstance from './render-instance'; const resolveAndMap = (ctx:RenderContext, resolver:Resolver) => { const mapAndRender = compose(renderInstance, ctx.instanceMapper); const resolve = resolver(ctx.componentMapper, mapAndRender); return compose( // 4) MAP the ResolvedComponent (optional). ctx.resolveMapper, // 3) RESOLVE the RenderedComponent into ResolvedComponent by applying a // resolve strategy (deep, shallow, interleaved). The result replaces // ShallowReactComponents with either a unknown components or HTML // components. The resolution process applies a ComponentMapper to each // RenderedComponent before resolving. resolve ); }; export const applyRootPipeline = (ctx:RenderContext, resolver:Resolver) => compose( resolveAndMap(ctx, resolver), // 2) RENDER the React INSTANCE into a RenderedComponent (renderInstance) // Note: at this stage, the RenderedComponent tree includes // not-yet-rendered ShallowReactComponents. renderInstance, // 1) MAP the React INSTANCE (optional). ctx.instanceMapper ); const failShallowArtificial = (comp:HtmlComponent) => (_:ReactInstance) => { throw ` Cannot call renderNew on shallow rendered component ${comp.tagName}. Looks like you are trying test using shallow render. Consider using another rendering mode (e.g. interleaved) or refactoring the test to do a shallow stateless test of and a shallow stateful test of ; `; }; export const applyComponentPipeline = (ctx:RenderContext, resolver:Resolver) => (comp:HtmlComponent) => compose( resolveAndMap(ctx, resolver), // 2) RE-RENDER the React INSTANCE into a RenderedComponent (renderNew) // Note: at this stage, the RenderedComponent tree includes // not-yet-rendered ShallowReactComponents. comp.renderNew || failShallowArtificial(comp), // 1) MAP the React INSTANCE (optional). ctx.instanceMapper ); ================================================ FILE: src/react-unit.ts ================================================ import * as R from 'ramda'; import { UnitComponent } from './unit-component'; import { ReactInstance , RenderContext , Resolver , ResolvedComponent , AddOn , defaultRenderContext , isUnknown } from './types'; import { deepResolver , shallowResolver , interleavedResolver } from './resolver'; import { applyRootPipeline, applyComponentPipeline } from './pipeline'; import wrapper from './wrapper'; import addOns from './add-ons'; interface CreateComponentFn { (instance:ReactInstance):UnitComponent|any } interface CreateComponent extends CreateComponentFn { shallow: CreateComponentFn interleaved: CreateComponentFn ctx: RenderContext } const createComponent = ( ctx:RenderContext, resolver:Resolver ):(instance:ReactInstance) => UnitComponent|any => { const rootPipeline = applyRootPipeline(ctx, resolver); const componentPipeline = applyComponentPipeline(ctx, resolver); function wrap(resolved:ResolvedComponent):UnitComponent|any { return isUnknown(resolved) ? resolved.unknown : wrapper(undefined, resolved, c => i => wrap(componentPipeline(c)(i))); } return R.compose(wrap, rootPipeline); }; const makeCreateComponent = (ctx:RenderContext) => { const fn:CreateComponent = createComponent(ctx, deepResolver) as any; fn.shallow = createComponent(ctx, shallowResolver); fn.interleaved = createComponent(ctx, interleavedResolver); fn.ctx = ctx; return fn; }; function applyAddons(fn:CreateComponent) { R.toPairs(addOns).forEach(([name, addOn]) => { (fn as any)[name] = R.compose(applyAddons, makeCreateComponent, addOn(fn.ctx)); }); return fn; }; export = applyAddons(makeCreateComponent(defaultRenderContext)); ================================================ FILE: src/render-instance.ts ================================================ import { Children } from 'react'; import { mergeAll } from 'ramda'; import ReactShallowRenderer = require('react-test-renderer/shallow'); import { ReactInstance , RenderedComponent , ShallowReactComponent , RenderedHtmlComponent , ArtificialHtmlComponent , RenderedUnknownComponent , RenderNew , isHtml } from './types'; import { getTagNameForType } from './utils'; // --- Rendered Component Wrapper Functions --------------------------------- // function wrapShallowReactComponent( renderOutput:any ):ShallowReactComponent { const { type } = renderOutput; const tagName = getTagNameForType(type); return { type: 'shallow', tagName, instance: renderOutput }; }; const getPropsForOutput = ( { key , ref , props , _store // tslint:disable-line }:any ) => mergeAll([ _store && _store.props, props, { key, ref } ]) as any; function wrapHtmlComponent( renderNew:RenderNew, renderOutput:any, instance:ReactInstance ):RenderedHtmlComponent { const { type, key, ref } = renderOutput; const props = getPropsForOutput(renderOutput); const children:RenderedComponent[] = props.children ? Children.map( props.children, c => processRenderOutput(renderNew, c, c) ) : []; return { type: 'html', tagName: type, key, ref, props: props, renderOutput, instance, children, renderNew }; }; function wrapUnknownComponent( renderOutput:any ):RenderedUnknownComponent { return { type: 'unknown', unknown: renderOutput }; } function processRenderOutput( renderNew:RenderNew, renderOutput:any, instance:any ):RenderedComponent { if (!renderOutput) { // e.g. render() { return undefined; } return wrapUnknownComponent(renderOutput); } if (typeof renderOutput.type === 'function') { // shallowRender reached another React component and stopped // renderOutput is the spec for that (child) component return wrapShallowReactComponent(renderOutput); } if (renderOutput.type && renderOutput.props) { // shallowRender returned an HTML component return wrapHtmlComponent(renderNew, renderOutput, instance); } return wrapUnknownComponent(renderOutput); } // --- Public API ----------------------------------------------------------- // export const toArtificialHtml = ( comp:ShallowReactComponent|RenderedHtmlComponent, child?:RenderedComponent ):ArtificialHtmlComponent => ({ type: 'artificial', tagName: getTagNameForType(comp.instance.type), props: getPropsForOutput(comp.instance), renderOutput: comp.instance, instance: comp.instance, children: child ? [child] : [], renderNew: child && isHtml(child) ? child.renderNew : undefined }); const renderInstance = (instance:ReactInstance):RenderedComponent => { const shallowRenderer = new ReactShallowRenderer(); function create(componentInstance:ReactInstance):RenderedComponent { shallowRenderer.render(componentInstance, componentInstance.context); const renderOutput = shallowRenderer.getRenderOutput(); const renderNew = (newInstance?:ReactInstance) => create(newInstance || componentInstance); return processRenderOutput(renderNew, renderOutput, componentInstance); } return create(instance); }; export default renderInstance; ================================================ FILE: src/resolver.ts ================================================ import { merge } from 'ramda'; import { toArtificialHtml } from './render-instance'; import { ComponentMapper , InstanceRenderer , RenderedComponent , ResolvedComponent , isHtml , isShallow } from './types'; export function deepResolver( mapper:ComponentMapper, renderer:InstanceRenderer ) { return (comp:RenderedComponent):ResolvedComponent => { const resolve = deepResolver(mapper, renderer); const mapped = mapper(comp); if (isShallow(mapped)) { const newComp = renderer(mapped.instance); // note that newComp might be shallow again, so we need to call // deepResolver on it to resolve the whole the tree return resolve(newComp); } if (isHtml(mapped)) return merge(mapped, { children: mapped.children.map(resolve) }); return mapped; }; } export function shallowResolver( mapper:ComponentMapper, _:InstanceRenderer ) { return (comp:RenderedComponent):ResolvedComponent => { const resolve = shallowResolver(mapper, _); const mapped = mapper(comp); if (isShallow(mapped)) { return toArtificialHtml(mapped); } if (isHtml(mapped)) { return merge(mapped, { children: mapped.children.map(resolve) }); } return mapped; }; } export function interleavedResolver( mapper:ComponentMapper, renderer:InstanceRenderer, nonRoot?:boolean ) { return (comp:RenderedComponent):ResolvedComponent => { const resolve = interleavedResolver(mapper, renderer, true); const mapped = mapper(comp); if (isShallow(mapped)) { const newComp = renderer(mapped.instance); // note that newComp might be shallow again, so we need to call // interleavedResolver on it to render the whole the tree const renderedComp = resolve(newComp); return toArtificialHtml(mapped, renderedComp); } if (isHtml(mapped)) { const mappedWithChildren = merge(mapped, { children: mapped.children.map(resolve) }); return nonRoot ? mappedWithChildren : toArtificialHtml(mappedWithChildren, mappedWithChildren); } return mapped; }; } ================================================ FILE: src/sizzle-bundle.d.ts ================================================ declare function lookup(s:string, comp:any):any[]; export = lookup; ================================================ FILE: src/sizzle-bundle.js ================================================ var document = { createElement: function(tag) { return { appendChild: function() {}, getElementsByTagName: function() { return []; }, getAttribute: function() {} } }, createComment: function(s) { return s; }, getElementsByClassName: ' { [native code] }', nodeType: 9, documentElement: { nodeName: 'HTML' } }; document.ownerDocument = document; var window = { document: document }; var _ = (function() { /*! * Sizzle CSS Selector Engine v2.2.1-pre * http://sizzlejs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2015-08-17 */ (function( window ) { var i, support, Expr, getText, isXML, tokenize, compile, select, outermostContext, sortInput, hasDuplicate, // Local document vars setDocument, document, docElem, documentIsHTML, rbuggyQSA, rbuggyMatches, matches, contains, // Instance-specific data expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; }, // General-purpose constants MAX_NEGATIVE = 1 << 31, // Instance methods hasOwn = ({}).hasOwnProperty, arr = [], pop = arr.pop, push_native = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf as it's faster than native // http://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { if ( list[i] === elem ) { return i; } } return -1; }, booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { "ID": new RegExp( "^#(" + identifier + ")" ), "CLASS": new RegExp( "^\\.(" + identifier + ")" ), "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, rnative = /^[^{]+\{\s*\[native \w/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, rescape = /'|\\/g, // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), funescape = function( _, escaped, escapedWhitespace ) { var high = "0x" + escaped - 0x10000; // NaN means non-codepoint // Support: Firefox<24 // Workaround erroneous numeric interpretation of +"0x" return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint String.fromCharCode( high + 0x10000 ) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }; // Optimize for push.apply( _, NodeList ) try { push.apply( (arr = slice.call( preferredDoc.childNodes )), preferredDoc.childNodes ); // Support: Android<4.0 // Detect silently failing push.apply arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { push_native.apply( target, slice.call(els) ); } : // Support: IE<9 // Otherwise append directly function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length while ( (target[j++] = els[i++]) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; } // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // ID selector if ( (m = match[1]) ) { // Document context if ( nodeType === 9 ) { if ( (elem = context.getElementById( m )) ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } // Element context } else { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( newContext && (elem = newContext.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Type selector } else if ( match[2] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll if ( support.qsa && !compilerCache[ selector + " " ] && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { if ( nodeType !== 1 ) { newContext = context; newSelector = selector; // qSA looks outside Element context, which is not what we want // Thanks to Andrew Dupont for this workaround technique // Support: IE <=8 // Exclude object elements } else if ( context.nodeName.toLowerCase() !== "object" ) { // Capture the context ID, setting it first if necessary if ( (nid = context.getAttribute( "id" )) ) { nid = nid.replace( rescape, "\\CONTENT" ); } else { context.setAttribute( "id", (nid = expando) ); } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { groups[i] = "[id='" + nid + "'] " + toSelector( groups[i] ); } newSelector = groups.join( "," ); // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; } if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch ( qsaError ) { } finally { if ( nid === expando ) { context.removeAttribute( "id" ); } } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); } /** * Create key-value caches of limited size * @returns {function(string, object)} Returns the Object data after storing it on itself with * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) * deleting the oldest entry */ function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } return (cache[ key + " " ] = value); } return cache; } /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } /** * Support testing using an element * @param {Function} fn Passed the created div and expects a boolean result */ function assert( fn ) { var div = document.createElement("div"); try { return !!fn( div ); } catch (e) { return false; } finally { // Remove from its parent by default if ( div.parentNode ) { div.parentNode.removeChild( div ); } // release memory in IE div = null; } } /** * Adds the same handler for all of the specified attrs * @param {String} attrs Pipe-separated list of attributes * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { var arr = attrs.split("|"), i = attrs.length; while ( i-- ) { Expr.attrHandle[ arr[i] ] = handler; } } /** * Checks document order of two siblings * @param {Element} a * @param {Element} b * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b */ function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); // Use IE sourceIndex if available on both nodes if ( diff ) { return diff; } // Check if b follows a if ( cur ) { while ( (cur = cur.nextSibling) ) { if ( cur === b ) { return -1; } } } return a ? 1 : -1; } /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === type; }; } /** * Returns a function to use in pseudos for buttons * @param {String} type */ function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && elem.type === type; }; } /** * Returns a function to use in pseudos for positionals * @param {Function} fn */ function createPositionalPseudo( fn ) { return markFunction(function( argument ) { argument = +argument; return markFunction(function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { if ( seed[ (j = matchIndexes[i]) ] ) { seed[j] = !(matches[j] = seed[j]); } } }); }); } /** * Checks a node for validity as a Sizzle context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience support = Sizzle.support = {}; /** * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = elem && (elem.ownerDocument || elem).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; /** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { var hasCompare, parent, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML( document ); // Support: IE 9-11, Edge // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) if ( (parent = document.defaultView) && parent.top !== parent ) { // Support: IE 11 if ( parent.addEventListener ) { parent.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only } else if ( parent.attachEvent ) { parent.attachEvent( "onunload", unloadHandler ); } } /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert(function( div ) { div.className = "i"; return !div.getAttribute("className"); }); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert(function( div ) { div.appendChild( document.createComment("") ); return !div.getElementsByTagName("*").length; }); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programatically-set names, // so use a roundabout getElementsByName test support.getById = assert(function( div ) { docElem.appendChild( div ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; }); // ID find and filter if ( support.getById ) { Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var m = context.getElementById( id ); return m ? [ m ] : []; } }; Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute("id") === attrId; }; }; } else { // Support: IE6/7 // getElementById is not reliable as a find shortcut delete Expr.find["ID"]; Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return node && node.value === attrId; }; }; } // Tag Expr.find["TAG"] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); // DocumentFragment nodes don't have gEBTN } else if ( support.qsa ) { return context.querySelectorAll( tag ); } } : function( tag, context ) { var elem, tmp = [], i = 0, // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { while ( (elem = results[i++]) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } } return tmp; } return results; }; // Class Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; /* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See http://bugs.jquery.com/ticket/13378 rbuggyQSA = []; if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { // Build QSA regex // Regex strategy adopted from Diego Perini assert(function( div ) { // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // http://bugs.jquery.com/ticket/12359 docElem.appendChild( div ).innerHTML = "" + ""; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section if ( div.querySelectorAll("[msallowcapture^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly if ( !div.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { rbuggyQSA.push("~="); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !div.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibing-combinator selector` fails if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { rbuggyQSA.push(".#.+[+~]"); } }); assert(function( div ) { // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment var input = document.createElement("input"); input.setAttribute( "type", "hidden" ); div.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute if ( div.querySelectorAll("[name=d]").length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests if ( !div.querySelectorAll(":enabled").length ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Opera 10-11 does not throw on post-comma invalid pseudos div.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); } if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector) )) ) { assert(function( div ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( div, "div" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( div, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); }); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully self-exclusive // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // Choose the first element that is related to our preferred document if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { return -1; } if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { return 1; } // Maintain original order return sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison cur = a; while ( (cur = cur.parentNode) ) { ap.unshift( cur ); } cur = b; while ( (cur = cur.parentNode) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[i] === bp[i] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[i], bp[i] ) : // Otherwise nodes in our document sort first ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; }; return document; }; Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); }; Sizzle.matchesSelector = function( elem, expr ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } // Make sure that attribute selectors are quoted expr = expr.replace( rattributeQuotes, "='$1']" ); if ( support.matchesSelector && documentIsHTML && !compilerCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch (e) {} } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { // Set document vars if needed if ( ( context.ownerDocument || context ) !== document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Document sorting and removing duplicates * @param {ArrayLike} results */ Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { while ( (elem = results[i++]) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } // Clear input after sorting to release objects // See https://github.com/jquery/sizzle/pull/225 sortInput = null; return results; }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // If no nodeType, this is expected to be an array while ( (node = elem[i++]) ) { // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } // Do not include comment or processing instruction nodes return ret; }; Expr = Sizzle.selectors = { // Can be adjusted by the user cacheLength: 50, createPseudo: markFunction, match: matchExpr, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }, preFilter: { "ATTR": function( match ) { match[1] = match[1].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); if ( match[2] === "~=" ) { match[3] = " " + match[3] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[1] = match[1].toLowerCase(); if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument if ( !match[3] ) { Sizzle.error( match[0] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); // other types prohibit arguments } else if ( match[3] ) { Sizzle.error( match[0] ); } return match; }, "PSEUDO": function( match ) { var excess, unquoted = !match[6] && match[2]; if ( matchExpr["CHILD"].test( match[0] ) ) { return null; } // Accept quoted arguments as-is if ( match[3] ) { match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && // Get excess from tokenize (recursively) (excess = tokenize( unquoted, true )) && // advance to the next closing parenthesis (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { // excess is a negative index match[0] = match[0].slice( 0, excess ); match[2] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) return match.slice( 0, 3 ); } }, filter: { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; }, "CLASS": function( className ) { var pattern = classCache[ className + " " ]; return pattern || (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && classCache( className, function( elem ) { return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); }); }, "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } result += ""; return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; }; }, "CHILD": function( type, what, argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; return first === 1 && last === 0 ? // Shortcut for :nth-*(n) function( elem ) { return !!elem.parentNode; } : function( elem, context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), useCache = !xml && !ofType, diff = false; if ( parent ) { // :(first|last|only)-(child|of-type) if ( simple ) { while ( dir ) { node = elem; while ( (node = node[ dir ]) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { return false; } } // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } return true; } start = [ forward ? parent.firstChild : parent.lastChild ]; // non-xml :nth-child(...) stores cache data on `parent` if ( forward && useCache ) { // Seek `elem` from a previously-cached index // ...in a gzip-friendly way node = parent; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; while ( (node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start (diff = nodeIndex = 0) || start.pop()) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } } else { // Use previously-cached element index if available if ( useCache ) { // ...in a gzip-friendly way node = elem; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { // Use the same loop as above to seek `elem` from the start while ( (node = ++nodeIndex && node && node[ dir ] || (diff = nodeIndex = 0) || start.pop()) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { // Cache the index of each encountered element if ( useCache ) { outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); uniqueCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { break; } } } } } // Incorporate the offset, then check against cycle size diff -= last; return diff === first || ( diff % first === 0 && diff / first >= 0 ); } }; }, "PSEUDO": function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Sizzle does if ( fn[ expando ] ) { return fn( argument ); } // But maintain support for old signatures if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? markFunction(function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { idx = indexOf( seed, matched[i] ); seed[ idx ] = !( matches[ idx ] = matched[i] ); } }) : function( elem ) { return fn( elem, 0, args ); }; } return fn; } }, pseudos: { // Potentially complex pseudos "not": markFunction(function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? markFunction(function( seed, matches, context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { if ( (elem = unmatched[i]) ) { seed[i] = !(matches[i] = elem); } } }) : function( elem, context, xml ) { input[0] = elem; matcher( input, null, xml, results ); // Don't keep the element (issue #299) input[0] = null; return !results.pop(); }; }), "has": markFunction(function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; }), "contains": markFunction(function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; }; }), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test(lang || "") ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { if ( (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); return false; }; }), // Miscellaneous "target": function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, "root": function( elem ) { return elem === docElem; }, "focus": function( elem ) { return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); }, // Boolean properties "enabled": function( elem ) { return elem.disabled === false; }, "disabled": function( elem ) { return elem.disabled === true; }, "checked": function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); }, "selected": function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, // Contents "empty": function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { if ( elem.nodeType < 6 ) { return false; } } return true; }, "parent": function( elem ) { return !Expr.pseudos["empty"]( elem ); }, // Element/input types "header": function( elem ) { return rheader.test( elem.nodeName ); }, "input": function( elem ) { return rinputs.test( elem.nodeName ); }, "button": function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, "text": function( elem ) { var attr; return elem.nodeName.toLowerCase() === "input" && elem.type === "text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection "first": createPositionalPseudo(function() { return [ 0 ]; }), "last": createPositionalPseudo(function( matchIndexes, length ) { return [ length - 1 ]; }), "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; }), "even": createPositionalPseudo(function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "odd": createPositionalPseudo(function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; }), "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; }) } }; Expr.pseudos["nth"] = Expr.pseudos["eq"]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { Expr.pseudos[ i ] = createInputPseudo( i ); } for ( i in { submit: true, reset: true } ) { Expr.pseudos[ i ] = createButtonPseudo( i ); } // Easy API for creating new setFilters function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); tokenize = Sizzle.tokenize = function( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); } soFar = selector; groups = []; preFilters = Expr.preFilter; while ( soFar ) { // Comma and first run if ( !matched || (match = rcomma.exec( soFar )) ) { if ( match ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[0].length ) || soFar; } groups.push( (tokens = []) ); } matched = false; // Combinators if ( (match = rcombinators.exec( soFar )) ) { matched = match.shift(); tokens.push({ value: matched, // Cast descendant combinators to space type: match[0].replace( rtrim, " " ) }); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || (match = preFilters[ type ]( match ))) ) { matched = match.shift(); tokens.push({ value: matched, type: type, matches: match }); soFar = soFar.slice( matched.length ); } } if ( !matched ) { break; } } // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens return parseOnly ? soFar.length : soFar ? Sizzle.error( selector ) : // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; function toSelector( tokens ) { var i = 0, len = tokens.length, selector = ""; for ( ; i < len; i++ ) { selector += tokens[i].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, checkNonElements = base && dir === "parentNode", doneName = done++; return combinator.first ? // Check against closest ancestor/preceding element function( elem, context, xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } } : // Check against all ancestor/preceding elements function( elem, context, xml ) { var oldCache, uniqueCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; } } } } else { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || (elem[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); if ( (oldCache = uniqueCache[ dir ]) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements return (newCache[ 2 ] = oldCache[ 2 ]); } else { // Reuse newcache so results back-propagate to previous elements uniqueCache[ dir ] = newCache; // A match means we're done; a fail means we have to keep checking if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { return true; } } } } } }; } function elementMatcher( matchers ) { return matchers.length > 1 ? function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { if ( !matchers[i]( elem, context, xml ) ) { return false; } } return true; } : matchers[0]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { Sizzle( selector, contexts[i], results ); } return results; } function condense( unmatched, map, filter, context, xml ) { var elem, newUnmatched = [], i = 0, len = unmatched.length, mapped = map != null; for ( ; i < len; i++ ) { if ( (elem = unmatched[i]) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { map.push( i ); } } } } return newUnmatched; } function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } return markFunction(function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } // Apply postFilter if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( (elem = temp[i]) ) { matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); } } } if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) ) { // Restore matcherIn since elem is not yet a final match temp.push( (matcherIn[i] = elem) ); } } postFinder( null, (matcherOut = []), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) && (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { postFinder( null, results, matcherOut, xml ); } else { push.apply( results, matcherOut ); } } }); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, leadingRelative = Expr.relative[ tokens[0].type ], implicitRelative = leadingRelative || Expr.relative[" "], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) matchContext = addCombinator( function( elem ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( (checkContext = context).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { if ( (matcher = Expr.relative[ tokens[i].type ]) ) { matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; } else { matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[j].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } return elementMatcher( matchers ); } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, matchedCount = 0, i = "0", unmatched = seed && [], setMatched = [], contextBackup = outermostContext, // We must always have either seed elements or outermost context elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), len = elems.length; if ( outermost ) { outermostContext = context === document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id for ( ; i !== len && (elem = elems[i]) != null; i++ ) { if ( byElement && elem ) { j = 0; if ( !context && elem.ownerDocument !== document ) { setDocument( elem ); xml = !documentIsHTML; } while ( (matcher = elementMatchers[j++]) ) { if ( matcher( elem, context || document, xml) ) { results.push( elem ); break; } } if ( outermost ) { dirruns = dirrunsUnique; } } // Track unmatched elements for set filters if ( bySet ) { // They will have gone through all possible matchers if ( (elem = !matcher && elem) ) { matchedCount--; } // Lengthen the array for every element, matched or not if ( seed ) { unmatched.push( elem ); } } } // `i` is now the count of elements visited above, and adding it to `matchedCount` // makes the latter nonnegative. matchedCount += i; // Apply set filters to unmatched elements // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` // equals `i`), unless we didn't visit _any_ elements in the above loop because we have // no element matchers and no seed. // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that // case, which will result in a "00" `matchedCount` that differs from `i` but is also // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; while ( (matcher = setMatchers[j++]) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { if ( !(unmatched[i] || setMatched[i]) ) { setMatched[i] = pop.call( results ); } } } // Discard index placeholder values to get only actual matches setMatched = condense( setMatched ); } // Add matches to results push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { Sizzle.uniqueSort( results ); } } // Override manipulation of globals by nested matchers if ( outermost ) { dirruns = dirrunsUnique; outermostContext = contextBackup; } return unmatched; }; return bySet ? markFunction( superMatcher ) : superMatcher; } compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { cached = matcherFromTokens( match[i] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { elementMatchers.push( cached ); } } // Cache the compiled function cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; }; /** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( (selector = compiled.selector || selector) ); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees us context) if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID tokens = match[0] = match[0].slice( 0 ); if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && support.getById && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; if ( !context ) { return results; // Precompiled matchers will still verify ancestry, so step up a level } else if ( compiled ) { context = context.parentNode; } selector = selector.slice( tokens.shift().value.length ); } // Fetch a seed set for right-to-left matching i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[i]; // Abort if we hit a combinator if ( Expr.relative[ (type = token.type) ] ) { break; } if ( (find = Expr.find[ type ]) ) { // Search, expanding context for leading sibling combinators if ( (seed = find( token.matches[0].replace( runescape, funescape ), rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context )) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above ( compiled || compile( selector, match ) )( seed, context, !documentIsHTML, results, !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }; // One-time assignments // Sort stability support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* support.sortDetached = assert(function( div1 ) { // Should return 1, but returns 4 (following) return div1.compareDocumentPosition( document.createElement("div") ) & 1; }); // Support: IE<8 // Prevent attribute/property "interpolation" // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !assert(function( div ) { div.innerHTML = ""; return div.firstChild.getAttribute("href") === "#" ; }) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } }); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert(function( div ) { div.innerHTML = ""; div.firstChild.setAttribute( "value", "" ); return div.firstChild.getAttribute( "value" ) === ""; }) ) { addHandle( "value", function( elem, name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } }); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies if ( !assert(function( div ) { return div.getAttribute("disabled") == null; }) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : (val = elem.getAttributeNode( name )) && val.specified ? val.value : null; } }); } // EXPOSE if ( typeof define === "function" && define.amd ) { define(function() { return Sizzle; }); // Sizzle requires that there be a global window in Common-JS like environments } else if ( typeof module !== "undefined" && module.exports ) { module.exports = Sizzle; } else { window.Sizzle = Sizzle; } // EXPOSE })( window ); ; })(window); ================================================ FILE: src/types.ts ================================================ // The goal of react-unit is to process react `instances` and turn them into // `UnitComponents`. So, when all is said and done,react-unit looks like: // // function createComponent(instance:ReactInstance):UnitComponent; // // UnitComponent is a bit complex and deserves a whole file, see // `unit-component.ts`. // ReactInstance: this is the return type of React.createElement, that is: // // const instance = ; // export interface ReactInstance { type: string & { name?:string, displayName?:string } context?: any // more secret stuff used by React's (shallow) render } // To apply this transformation, we use a pipeline of different steps: // === Pipeline ============================================================= // // 1) We apply an optional transformation to the ReactInstance export interface InstanceMapper { (instance:ReactInstance):ReactInstance } // 2) We render the instance into a shallow tree of rendered components export interface InstanceRenderer { (instance:ReactInstance):RenderedComponent } export type RenderedComponent = ShallowReactComponent | RenderedHtmlComponent | ArtificialHtmlComponent | RenderedUnknownComponent; // 3) We apply an optional transformation to the RenderedComponent export interface ComponentMapper { (comp:RenderedComponent):RenderedComponent } // 4) We resolve the rendered component by transforming shallow components into // something else. Different resolution strategies (see `resolver.ts`) will // do different things, like render the component, replace it or both. export interface Resolver { ( mapper:ComponentMapper , renderer:InstanceRenderer ):(comp:RenderedComponent) => ResolvedComponent } export type ResolvedComponent = RenderedUnknownComponent | RenderedHtmlComponent | ArtificialHtmlComponent; // 5) We apply an optional transformation to the resolved component export interface ResolvedMapper { (comp:ResolvedComponent):ResolvedComponent } // Putting it all together: export interface RenderContext { instanceMapper: InstanceMapper componentMapper: ComponentMapper resolveMapper: ResolvedMapper } export const defaultRenderContext:RenderContext = ({ instanceMapper: i => i, componentMapper: c => c, resolveMapper: r => r }); // === Add-ons ============================================================== // // Add-ons are just transformations to the render context using some // user-supplied arguments. export interface AddOn { (ctx:RenderContext):(...args:any[]) => RenderContext } // === Rendered Type specificatons ========================================== // // Models any React component (Stateless, Es2015 class, createClass, ...) that // was NOT rendered yet For example: // // React.createElement(MyComp, { ... }) export interface ShallowReactComponent { type: 'shallow' tagName: string // name / displayName of the component instance: ReactInstance // this can be rendered with renderInstance } // Models primitive HTML tags returned by a render() method interface HtmlComponentBase { tagName: string key?: any ref?: any props: any renderOutput: any instance: ReactInstance // this can be rendered with renderInstance children: RenderedComponent[] } export interface RenderedHtmlComponent extends HtmlComponentBase { type: 'html' renderNew: RenderNew } export interface ArtificialHtmlComponent extends HtmlComponentBase { type: 'artificial' renderNew?: RenderNew } // Models an unknown component (null, undefined, primitive types, ...) export interface RenderedUnknownComponent { type: 'unknown' unknown: any } export type HtmlComponent = RenderedHtmlComponent | ArtificialHtmlComponent; export interface RenderNew { (instance?:ReactInstance):RenderedComponent } // === Type guards ========================================================== // export const isShallow = ( c:RenderedComponent|ResolvedComponent ):c is ShallowReactComponent => c.type === 'shallow'; export const isHtml = ( c:RenderedComponent|ResolvedComponent ):c is RenderedHtmlComponent => c.type === 'html' || c.type === 'artificial'; export const isArtificialHtml = ( c:RenderedComponent|ResolvedComponent ):c is ArtificialHtmlComponent => c.type === 'artificial'; export const isUnknown = ( c:RenderedComponent|ResolvedComponent ):c is RenderedUnknownComponent => c.type === 'unknown'; ================================================ FILE: src/unit-component.ts ================================================ import * as R from 'ramda'; import { ReactInstance, HtmlComponent, isArtificialHtml } from './types'; import { isText, isOfType } from './utils'; import sizzle = require('./sizzle-bundle'); declare const global:any; type Root = 'root'; const ROOT:Root = 'root'; const isNotRoot = (c:UnitComponent|Root):c is UnitComponent => c !== ROOT; export interface RenderNewUnit { (instance:ReactInstance):UnitComponent } // Component wrapper export class UnitComponent { type: string; // tag name key: any; ref: any; props: any; text: string; texts: string[]; root: UnitComponent; parentNode: UnitComponent|undefined; ownerDocument: any; comp: HtmlComponent; nodeType = 1; nodeName: string; renderNew: RenderNewUnit|undefined; constructor( comp:HtmlComponent, parent:OptionalUnitComponent|Root, renderNew:RenderNewUnit|undefined ) { this.type = comp.tagName; this.key = comp.key; this.ref = comp.ref; this.props = comp.props; this.texts = []; this.comp = comp; this.renderNew = renderNew; // Mock root parent (to enable top-level "[attr=value]") if (!parent) { this.root = new UnitComponent({} as any, ROOT, undefined); this.root.props = { children: this }; this.parentNode = this.root; } else if (isNotRoot(parent)) { this.parentNode = parent; } // Mock DOM this.ownerDocument = global.document; this.nodeName = this.type; } prop(name:string) { return (this.props || {})[name]; } findBy(fn:(c:UnitComponent) => boolean):UnitComponent[] { const ret:UnitComponent[] = []; if (fn(this)) ret.push(this); let children = this.prop('children'); if (!children || isText(children)) return ret; if (children.length === undefined) children = [ children ]; return R.compose( R.concat(ret), R.filter((c:UnitComponent) => !!c), R.flatten, R.map((c:UnitComponent) => c.findBy && c.findBy(fn)) )(children); } findByRef(ref:any) { return this.findBy(c => c.ref == ref); } findByTag(type:string) { return this.findBy(type == '*' ? (c => true) : (c => c.type == type)); } findByClassName(search:string) { const pattern = new RegExp('(^|\\s)' + search + '(\\s|$)'); return this.findBy(e => pattern.test(e.prop('className'))); } findByComponent(componentClass:any) { return this.findBy(e => isArtificialHtml(e.comp) && isOfType(componentClass, e.comp) ); } on(event:string, e:any) { event = `on${event[0].toUpperCase() + event.slice(1)}`; const handler = this.props[event]; if (!handler) throw `Triggered unhandled ${event} event: ${new Error().stack}`; return handler(e); } onChange(e:any) { this.on('change', e); } onClick(e:any) { this.on('click', e); } setValueKey(n:string, v:any ) { this.onChange({ target: R.merge(this.props, {[n]: v}) }); } setValue(v:any) { this.setValueKey('value', v); } setChecked(v:boolean) { this.setValueKey('checked', v); } findByQuery(s:string):UnitComponent[] { try { return sizzle(s, this.root || this); } catch (e) { console.log('Sizzle error', e.stack); throw e; } } dump(padd:string|undefined) { if (!padd) padd = ''; let children = this.prop('children'); const tag = this.type + R.compose( R.join(''), R.map(([k, v]:[any, any]) => ` ${k}='${v}'`), R.filter(([_, v]:[any, any]) => isText(v) && (!!v || (v as any) === 0)), R.toPairs, R.merge({ key: this.key, ref: this.ref }), R.omit(['children']) )(this.props); if (!children || children.length === 0) { return this.text ? `${padd}<${tag}>${this.text}\n` : `${padd}<${tag} />\n`; } if (isText(children)) { return `${padd}<${tag}>${children}\n`; } if (children.length === undefined) children = [ children ]; const texts = children .map((c:UnitComponent) => c.dump(padd + ' ')) .join(''); return `${padd}<${tag}>\n${texts}${padd}\n`; } get children() { return this.props.children; } get textContent() { return this.text; } getElementsByTagName(tagName:string) { return this.findByTag(tagName); } getElementsByClassName(className:string) { return this.findByClassName(className); }; getAttribute(name:string) { return this.prop(name == 'class' ? 'className' : name); } getAttributeNode(name:string) { const prop = this.prop(name); return { value: prop, specified: prop !== undefined }; }; } export type OptionalUnitComponent = UnitComponent | undefined; ================================================ FILE: src/utils.ts ================================================ import { contains, merge } from 'ramda'; import { isElementOfType } from 'react-dom/test-utils'; import { RenderedComponent , isHtml , isShallow } from './types'; const notText = ['object', 'function']; export const isText = (v:any):v is string => !contains(typeof v, notText); export const isOfType = (type:any, comp:RenderedComponent):boolean => (isHtml(comp) || isShallow(comp)) && isElementOfType(comp.instance, type); export const getTagNameForType = (type:any) => type.displayName || type.name || 'anonymous-component'; export function filterChildren( fn:(c:RenderedComponent) => boolean, comp:RenderedComponent ):RenderedComponent { return isHtml(comp) ? merge(comp, { children: comp.children.filter(fn).map(c => filterChildren(fn, c)) }) : comp; } ================================================ FILE: src/wrapper.ts ================================================ import { HtmlComponent , isHtml , isUnknown } from './types'; import { UnitComponent , OptionalUnitComponent , RenderNewUnit } from './unit-component'; function wrapRenderedIntoUnitComponent( parent:OptionalUnitComponent, htmlComponent:HtmlComponent, componentPipeline:(comp:HtmlComponent) => RenderNewUnit ):UnitComponent { const unitComponent = new UnitComponent( htmlComponent, parent, componentPipeline(htmlComponent) ); let children:UnitComponent[] = []; let texts:string[] = []; htmlComponent.children.forEach(c => { if (isUnknown(c)) { const { unknown } = c; if (unknown !== undefined && unknown !== null) { texts.push(unknown.toString()); } } else if (isHtml(c)) { const child = wrapRenderedIntoUnitComponent( unitComponent, c, componentPipeline ); children.push(child); texts = texts.concat(child.texts); } // we ignore NotRenderedReactComponents }); unitComponent.props.children = children; unitComponent.texts = texts; unitComponent.text = texts.join(''); return unitComponent; } export default wrapRenderedIntoUnitComponent; ================================================ FILE: test/create-component-interleaved.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const R = require('ramda'); const Child = ({ title }) =>

                {title}

                ; const Master = () =>
                ; const SuperMaster = () => ; const MasterList = ({ titles }) =>
                  {titles.map((t,i) =>
                • )}
                ; const titles = [ 'Raiders of the Lost Ark', 'Temple of Doom', 'Last Crusade' ]; const Person = ({ name,children }) =>

                {name}

                  {React.Children.map(children, (c,i) =>
                • {c}
                • )}
                ; describe('createComponent.interleaved', () => { it('renders recursively, preserving components', () => { const component = createComponent.interleaved( ); const lisaComp = component.findByQuery('Person[name=Lisa]')[0]; const lisaCompAlt = component.findByComponent(Person)[2]; const lisaName = component.findByQuery('Person[name=Lisa] h1')[0]; const lisaNameAlt = lisaComp.findByQuery('h1')[0]; expect(lisaComp.prop('name')).toEqual('Lisa'); expect(lisaComp).toBe(lisaCompAlt); expect(lisaName.text).toEqual('Lisa'); expect(lisaNameAlt).toBe(lisaName); }); it('should find component in deeply nested components', () => { const component = createComponent.interleaved(); const results = component.findByQuery('Child'); expect(results.length).toEqual(1); }); it('should expose the props from the component', () => { const component = createComponent.interleaved(); const results = component.findByQuery('Child')[0]; expect(results.prop('title')).toEqual('First Child'); }); it('should render the actual components', () => { const component = createComponent.interleaved(); const results = component.findByQuery('Child')[0]; expect(results.prop('title')).toEqual('First Child'); }); it('should render deep component trees', () => { const component = createComponent.interleaved(); const lis = component.findByQuery('li'); const children = component.findByQuery('li > Child'); const h1s = component.findByQuery('li > Child > h1'); expect(lis.length).toEqual(titles.length); expect(children.length).toEqual(titles.length); expect(h1s.length).toEqual(titles.length); }); }); ================================================ FILE: test/create-component-shallow.jsx ================================================ const createComponent = require('./react-unit'); const React = require('react'); const R = require('ramda'); const Child = ({ title }) =>

                {title}

                ; const Master = () =>
                ; const SuperMaster = () => ; const MasterList = ({ titles }) =>
                  {titles.map((t,i) =>
                • )}
                ; const titles = [ 'Raiders of the Lost Ark', 'Temple of Doom', 'Last Crusade' ]; const Person = ({ name,children }) =>

                {name}

                  {React.Children.map(children, (c,i) =>
                • {c}
                • )}
                ; describe('createComponent.shallow', () => { it('renders a single level of depth, preserving components', () => { const component = createComponent.shallow( ); const lisaByAttr = component.findByQuery('Person[name=Lisa]')[0]; const lisaByTagAndOrder = component.findByQuery('Person')[1]; const lisaByCompAndOrder = component.findByComponent(Person)[1]; expect(lisaByAttr.prop('name')).toEqual('Lisa'); expect(lisaByTagAndOrder.prop('name')).toEqual('Lisa'); expect(lisaByCompAndOrder.prop('name')).toEqual('Lisa'); }); it('should find direct descendent components', () => { const component = createComponent.shallow(); const results = component.findByQuery('Child'); expect(results.length).toEqual(1); }); it('should expose props from direct descendent components', () => { const component = createComponent.shallow(); const results = component.findByQuery('Child')[0]; expect(results.prop('title')).toEqual('First Child'); }); it('should not render child elements of direct descendent components', () => { const component = createComponent.shallow(); const results = component.findByQuery('h1'); expect(results.length).toEqual(0); }); it('should render HTML between components', () => { const component = createComponent.shallow(); const child = component.findByQuery('div > Child')[0]; expect(child).not.toBeUndefined(); }); it('should find component rendering just a string as children', () => { const Content = ({ children }) =>
                {children}
                ; const Page = () => Test; const component = createComponent.shallow(); expect(component.findByComponent(Content).length).toEqual(1); }); it('should find component passing the children down to child component', () => { const Content = ({ children }) =>
                {children}
                ; const Page = ({ children }) => {children}; const component = createComponent.shallow(
                Here
                ); expect(component.findByComponent(Content).length).toEqual(1); }); it('should allow findByQuery in component props', () => { const component = createComponent.shallow(); const child = component.findByQuery('Child[title="First Child"]')[0]; expect(child).not.toBeUndefined(); }); it('should render lists of direct descendent components', () => { const component = createComponent.shallow(); const results = component.findByQuery('Child'); expect(results.length).toEqual(titles.length); R.compose( R.forEach(([t,r]) => expect(r.prop('title')).toEqual(t)), R.zip(titles) )(results); }); }); ================================================ FILE: test/create-component.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const WithNullChildren = () =>
                • first child
                • {undefined}
                • other child
                ; const WithChildren = ({ child }) =>
                {child}
                ; const Person = ({ name,children }) =>

                {name}

                  {React.Children.map(children, (c,i) =>
                • {c}
                • )}
                ; const NullRender = () => null; describe('createComponent', () => { it('renders recursively, erasing components', () => { const component = createComponent( ); const lisa = component.findByQuery('div > ul > li > div > h1')[1]; expect(lisa.text).toEqual('Lisa'); const persons = component.findByQuery('Person'); expect(persons.length).toEqual(0); }); it('should work with a component that renders nothing', () => { const component = createComponent(); expect(component).toEqual(null); }); it('should work with null children', () => { const component = createComponent(); const lis = component.findByQuery('li'); expect(lis.length).toEqual(2); }); it('should load numeric children', () => { const component = createComponent(); expect(component.text).toEqual('1'); }); it('should load text children', () => { const component = createComponent(); expect(component.text).toEqual('hey!'); }); }); ================================================ FILE: test/events.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); class ToUppercaseInput extends React.Component { constructor(props, ctx) { super(props, ctx); this.onChange = this.onChange.bind(this); } onChange(e) { this.props.onChange({target:{ value: e.target.value.toUpperCase() }}); } render() { return } } class OnlyUppercaseInput extends React.Component { constructor(props, ctx) { super(props, ctx); this.onChange = this.onChange.bind(this); } onChange(e) { if (e.target.value != e.target.value.toUpperCase()) return; this.props.onChange({target:{ value: e.target.value }}); } render() { return } } class SimpleButton extends React.Component { constructor(props, ctx) { super(props, ctx); this.onClick = this.onClick.bind(this); } onClick(e) { // not using { target: { value: 'clicked' } } to show some variety this.props.onClick('clicked'); } render() { return } } describe('events', () => { it('should apply handler logic', () => { let changedValue; // to be updated by the component const component = createComponent( changedValue = e.target.value} />); // Trigger the change event const input = component.findByQuery('input')[0]; input.onChange({target: {value: 'HELLO, world'}}); expect(changedValue).toEqual('HELLO, WORLD'); }); it('might be cancelled', () => { let changedValue; // to be updated by the component const component = createComponent( changedValue = e.target.value} />); // Trigger the change event const input = component.findByQuery('input')[0]; input.onChange({target: {value: 'HELLO, world'}}); expect(changedValue).toBeUndefined(); }); it('can be called using shorthand methods (onClick and onChange)', () => { let message; // to be updated by the component const component = createComponent( message = e}/>); // Trigger the click event component.findByQuery('button')[0].onClick(); expect(message).toBe('clicked'); }); it('can be called using on(event)', () => { let message; // to be updated by the component const component = createComponent( message = e}/>); // Trigger the click event component.findByQuery('button')[0].on('click'); expect(message).toBe('clicked'); }); it('can be called directly using props', () => { let message; // to be updated by the component const component = createComponent( message = e}/>); // Trigger the click event component.findByQuery('button')[0].props.onClick(); expect(message).toBe('clicked'); }); }); ================================================ FILE: test/exclude.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const SubItem = ({ children }) => {children}; const Item = ({ children}) => {children}; const Items = ({ children}) =>
                {children}
                ; describe('exclude', () => { it('can "skip" a component using createComponent directly', () => { const component = createComponent.exclude(Item)( 1 2 3 4 ); const found = component.findByQuery('span:contains(1)'); expect(found.length).toEqual(0); }); it('can reverts skip after run once', () => { const component = createComponent( 1 2 3 4 ); const found = component.findByQuery('span:contains(1)'); expect(found.length).toEqual(1); }); it('can "skip" a component using createComponent.shallow', () => { const component = createComponent .exclude(Item) .shallow(); expect(component.findByComponent(Item).length).toEqual(0); }); it('can "skip" a component using createComponent.interleaved', () => { const component = createComponent.exclude(Item).interleaved( 1 2 3 4 ); const found = component.findByQuery('span:contains(1)'); expect(found.length).toEqual(0); }); it('does not affect nesting', () => { const component = createComponent( 1 Hello World 2 3 4 ); const found = component.findByQuery('i:contains("Hello World")'); expect(found.length).toEqual(1); }); it('can be nested and still exclude', () => { const component = createComponent.exclude(SubItem)( 1 Hello World 2 3 4 ); const found = component.findByQuery('i:contains("Hello World")'); expect(found.length).toEqual(0); }); it('can exclude multi', () => { const component = createComponent.exclude([Item, SubItem])( Hello World 1 2 3 4 ); expect(component.dump().replace(/[\n ]/g, '')).toEqual('
                '); }); it('dump is correct', () => { const component = createComponent.exclude(Item)( 1 2 3 4 ); expect(component.dump().replace(/[\n ]/g, '')).toEqual('
                '); }); }); ================================================ FILE: test/find-by-component.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const Child = ({ title }) =>

                {title}

                ; const Master = () =>
                ; const SuperMaster = () => ; describe('findByComponent', () => { it('should find component', () => { const component = createComponent.shallow(); const results = component.findByComponent(Child); expect(results.length).toEqual(1); }); it('should find component in deeply nested components', () => { const component = createComponent.interleaved(); const results = component.findByComponent(Child); expect(results.length).toEqual(1); }); it('should expose props from the Child component', () => { const component = createComponent.shallow(); const results = component.findByComponent(Child); expect(results[0].prop('title')).toEqual('First Child'); }); it('should expose the props from the component', () => { const component = createComponent.interleaved(); const results = component.findByComponent(Child); expect(results[0].prop('title')).toEqual('First Child'); }); }); ================================================ FILE: test/find-by-query.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const props = { value: 'found', onChange: function() {} } const ByTag = () => ; const ByClass = () => ; const ByAttr = () => ; const ByRelativeClass = () =>
                ; const ByTagAndAttr = () =>
                ; const ByTagComposite = () => ; const ByKey = () =>
                  {['a','b','c'].map(i =>
                • {i}
                • )}
                ; const ByRef = () =>
                ; const ByContains = () =>
                onetwothree
                ; describe('findByQuery', () => { it('should find by tag name', () => { const component = createComponent(); // Find every element with tag const input = component.findByQuery('input')[0]; expect(input.props.value).toEqual('found'); }); it('should find by class name', () => { const component = createComponent(); // Find every element with className="myClass" const input = component.findByQuery('.myClass')[0]; expect(input.props.value).toEqual('found'); }); it('should find by attribute', () => { const component = createComponent(); // Find every element with name="myName" const input = component.findByQuery('[name="myName"]')[0]; expect(input.props.value).toEqual('found'); }); it('should find by relative class name', () => { const component = createComponent(); // Find every element with className="myClass" inside a parent with // className="parent" const inputs = component.findByQuery('.parent .myClass'); expect(inputs.length).toEqual(1); expect(inputs[0].props.value).toEqual('found'); }); it('should find by class in nested trees returning depth order', () => { const component = createComponent(); // Find every element with className="myClass" const inputs = component.findByQuery('.myClass'); expect(inputs.length).toEqual(2); expect(inputs[0].props.value).toEqual('found'); expect(inputs[1].props.value).toEqual('not in parent!'); }); it('should find by tag and attribute', () => { const component = createComponent(); // Find every with name="myName" const inputs = component.findByQuery('input[name=myName]'); expect(inputs.length).toEqual(1); expect(inputs[0].props.value).toEqual('found'); }); it('should find by tag name in a composite component', () => { const component = createComponent(); // Find every element with tag const input = component.findByQuery('input')[0]; expect(input.props.value).toEqual('found'); }); it('should find by key', () => { const component = createComponent(); // Find every element with key="c" const input = component.findByQuery('[key=c]')[0]; expect(input.text).toEqual('c'); }); it('should find by ref', () => { const component = createComponent(); // Find every element with ref="myRef" const input = component.findByQuery('[ref=myRef]')[0]; expect(input.props.value).toEqual('found'); }); it('should find by contains', () => { const component = createComponent(); // Find elements that contains const span = component.findByQuery('div span:contains("three")')[0]; expect(span.textContent).toEqual('three'); }); }); ================================================ FILE: test/find-by-ref.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const Bouviers = () =>
                ; describe('findByRef', () => { it('should call the findBy method', () => { const component = createComponent(); spyOn(component, 'findBy'); component.findByRef('myRef'); expect(component.findBy).toHaveBeenCalled(); }); it('should find by ref attribute', () => { const component = createComponent(); const bouviers = component.findByRef('bouvier'); const simpsons = component.findByRef('simpson'); expect(bouviers.length).toEqual(3); expect(simpsons.length).toEqual(1); expect(simpsons[0].props.name).toEqual('Lisa Simpson'); }); }); ================================================ FILE: test/find-by.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const Bouviers = () =>
                ; describe('findBy', () => { it('can be used to find everything in depth order', () => { const component = createComponent(); // Find everything const everything = component.findBy(t => true); expect(everything.length).toEqual(4); expect(everything[0].props.name).toEqual('Jacqueline Bouvier'); expect(everything[1].props.name).toEqual('Marge Bouvier'); expect(everything[2].props.name).toEqual('Lisa Simpson'); expect(everything[3].props.name).toEqual('Patty Bouvier'); }); it('can be used with a filter function', () => { const component = createComponent(); // Find Bouviers const isBouvier = t => t.props.name.indexOf('Bouvier') != -1; const bouviers = component.findBy(isBouvier); expect(bouviers.length).toEqual(3); expect(bouviers[0].props.name).toEqual('Jacqueline Bouvier'); expect(bouviers[1].props.name).toEqual('Marge Bouvier'); expect(bouviers[2].props.name).toEqual('Patty Bouvier'); }); }); ================================================ FILE: test/internal/render-instance.jsx ================================================ const React = require('react'); import renderInstance from '../render-instance'; import { isHtml, isUnknown } from '../types'; const WithChildren = ({ child }) =>
                {child}
                ; describe('createComponent', () => { it('should load numeric children', () => { const renderedComponent = renderInstance(); expect(isHtml(renderedComponent)).toBe(true); const { children } = renderedComponent; expect(children.length).toBe(1); expect(isUnknown(children[0])).toBe(true); }); }); ================================================ FILE: test/mock.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const SubItem = ({ children}) => {children}; const Item = ({ children}) => {children}; const It = ({ children}) => {children}; const Items = ({ children}) =>
                {children}
                ; const Blink = ({ children}) => {children}; const Cite = ({ children}) => {children}; const Original = () => I'm the original; const Mock = () => I'm the mock; const Parent = () => ; describe('mock', () => { it('can mock components', () => { const component = createComponent.mock(Item, Blink)( 1 2 3 4 ); const found = component.findByQuery('blink'); const found1 = component.findByQuery('blink:contains(4)'); expect(found.length).toEqual(4); expect(found1.length).toEqual(1); expect(found1[0].dump().replace(/[\n ]/g, '')).toEqual('4'); }); it('can mock components repeatedly', () => { const component = createComponent .mock(Item, Blink) .mock(It, Cite)( 1 2 3 4 ); const found = component.findByQuery('div'); const foundBlinks = component.findByQuery('blink'); const foundCites = component.findByQuery('cite'); expect(found.length).toEqual(1); expect(foundBlinks.length).toEqual(2); expect(foundCites.length).toEqual(2); expect(found[0].dump().replace(/[\n ]/g, '
                1234
                ' )); }); it('can be combined with exclude', () => { const component = createComponent .mock(Item, Blink) .exclude(Cite)( Hey 1 2 ); const foundBlinks = component.findByQuery('blink'); const foundCites = component.findByQuery('cite'); expect(foundBlinks.length).toEqual(2); expect(foundCites.length).toEqual(0); expect(component.dump().replace(/[\n ]/g, '
                12
                ' )); }); it('understands findByComponent', () => { const component = createComponent .mock(Original, Mock) .interleaved (); expect(component.findByComponent(Original).length).toBe(0); expect(component.findByComponent(Mock).length).toBe(1); }) }); ================================================ FILE: test/render-new.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const R = require('ramda'); class ChangeAtoB extends React.Component { componentWillReceiveProps(newProps) { if (newProps.value == 'a') newProps.onChange('b'); } render() { return {this.props.value}; } } class Stateful extends React.Component { constructor(props, ctx) { super(props, ctx); this.state = { value: this.props.value }; this.onChange = this.onChange.bind(this); } onChange(e) { this.setState({value: e.target.value}); } render() { return ; } } describe('renderNew', () => { it('can be used to test componentWillReceiveProps', () => { let changedValue; // Refactor component description into a reusable function const newComponentDesc = props => { changedValue = undefined; return changedValue = cv} {...props} /> } // Create component (componentWillReceiveProps will NOT be called) const component = createComponent(newComponentDesc({value: 'a'})); // Assert that the value didn't change expect(changedValue).toBeUndefined(); // Now lets render a new version of the component changing the props... // NOTE: instead of calling createComponent() we are using // renderNew(), this method creates a copy of the EXISTNG // component, updating its properties (componentWillReceiveProps). const newComponent = component.renderNew(newComponentDesc({value: 'a'})); // Assert that the value changed to be (i.e. in componentWillReceiveProps) expect(changedValue).toBe('b'); }); const setAndAssertValue = (comp, value) => { const input = comp.findByQuery('input')[0]; input.onChange({target:{value: value}}); const newComp = comp.renderNew(); const newInput = newComp.findByQuery('input')[0]; expect(newInput.props.value).toEqual(value); return newComp; } it('can be chained in deep mode', () => { const component = createComponent(); const data = ['a','b','c','d','e','f','g']; R.reduce(setAndAssertValue, component, data); }); it('can be chained in interleaved mode', () => { const component = createComponent.interleaved(); const data = ['a','b','c','d','e','f','g']; R.reduce(setAndAssertValue, component, data); }); it('can be chained in shallow mode', () => { const component = createComponent.shallow(); const data = ['a','b','c','d','e','f','g']; R.reduce(setAndAssertValue, component, data); }); }); ================================================ FILE: test/stateful.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); class Stateful extends React.Component { constructor(props, ctx) { super(props, ctx); this.state = { value: this.props.value }; this.onChange = this.onChange.bind(this); } onChange(e) { this.setState({value: e.target.value}); } render() { return ; } } const StatefulWrapper = props =>
                ; class SetStateBeforeMount extends React.Component { constructor(props, ctx) { super(props, ctx); this.state = {mounted: 'false'}; } componentWillMount() { this.setState({mounted: 'true'}); } render() { return {this.state.mounted} } } describe('stateful controls', () => { it('should handle input changes', () => { const component = createComponent(); const input = component.findByQuery('input')[0]; input.onChange({target:{value: 'new!'}}); // Render the component with the new state into a new component: const newComponent = component.renderNew(); const newInput = newComponent.findByQuery('input')[0]; // Note that each time we render we get a new component with new // elements: expect(input).not.toBe(newInput); // And the original component remains unchanged: expect(input.props.value).toBe('original'); // But the new component did change: expect(newInput.props.value).toEqual('new!'); }); it('should handle input changes in nested components', () => { // We will create the component in interleaved mode so that we can access // the child component. const component = createComponent.interleaved( ); // You can uncomment the following line to se the result: // console.log(component.dump()); // Emit the onChange event. const input = component.findByQuery('input')[0]; input.onChange({target:{value: 'new!'}}); // Find the Stateful component (this only works in interleaved and shallow // mode, you could also try findByQuery('input') in the default mode). const stateful = component.findByQuery('Stateful')[0]; // or const stateful = component.findByComponent(Stateful)[0]; // or const stateful = component.findByQuery('input')[0]; // Note that we need to call renderNew on the Stateful component // and not on the root StatefulWrapper. const newStateful = stateful.renderNew(); const newInput = newStateful.findByQuery('input')[0]; expect(newInput.props.value).toEqual('new!'); }); it('can set state in their componentWillMount', () => { const component = createComponent(); expect(component.findByQuery('.status')[0].text).toEqual('true'); }); }); ================================================ FILE: test/stateless.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const FancyInput = ({ value, onChange }) => ; const FancyBorder = ({ children }) =>
                { children }
                ; describe('stateless components', () => { it('can be queried for child elements', () => { // Render the stateless component const component = createComponent(); // Find components on the rendered results const input = component.findByQuery('input')[0]; // Assert props expect(input.props.value).toBe("hi!"); expect(input.props.className).toBe("fancy"); }); it('can be queried for child components', () => { // Render the stateless component const component = createComponent( ); // Find components on the rendered results const border = component.findByQuery('div.fancy-border')[0]; // Note that we search components in directly in `border` const input = border.findByQuery('input')[0]; // Assert props expect(input).not.toBeUndefined(); }); it('can handle events', () => { // Here we'll store the change event let event; // Render the stateless component (note the onChange handler) const component = createComponent( event = e}/> ); // Find components on the rendered results const input = component.findByQuery('input')[0]; // Trigger the change event (e.g. simulating a user typing something) const e = {target:{value: 'new!'}}; input.onChange(e); // Assert the changed event expect(event).toBe(e); }); }); ================================================ FILE: test/text.jsx ================================================ // Note: you should use const createComponent = require('react-unit'); const createComponent = require('./react-unit'); const React = require('react'); const TextsWithinSingleElement = () =>
                {"Hello"} {"world"}{"!"}
                ; const TextsAccrossSeveralElements = () =>
                • 1
                • 2
                • 3
                ; const WithButton = () => ; describe('text or textContent', () => { it('returns the component text', () => { const component = createComponent(); const div = component.findByQuery('div')[0]; expect(div.text).toEqual('Hello world!'); expect(div.textContent).toEqual('Hello world!'); }); it('returns the aggregated text of the children with no extra spaces', () => { const component = createComponent(); const uls = component.findByQuery('ul')[0]; expect(uls.text).toEqual('123'); expect(uls.textContent).toEqual('123'); // In some scenarios you might want to assert the individual text elements // in these cases you could also do: expect(uls.texts.join(' ')).toEqual('1 2 3'); // or: expect(uls.texts).toEqual(['1', '2', '3']); }); it('returns the text of children buttons', () => { const component = createComponent(); const buttonWrapper = component.findByQuery('span')[0]; const button = component.findByQuery('button')[0]; expect(button.text).toEqual('Save'); expect(buttonWrapper.text).toEqual('Save'); }); }); ================================================ FILE: test/with-context.jsx ================================================ // Note: you should use: import createComponent from 'react-unit'; import createComponent from './react-unit'; const React = require('react'); import { string } from 'prop-types'; class Echo extends React.Component { constructor(props, ctx) { super(props, ctx); } render() { return {this.context.text} } } Echo.contextTypes = { text: string } class Child extends React.Component { render() { return {this.context && this.context.value || 'no-context'}; } } Child.contextTypes = { value: string }; const Parent = () =>
                ; describe('withContext', () => { it('passes the context to the rendered component', () => { const component = createComponent .withContext({ text: 'hi!' }) (); expect(component.textContent) .toEqual('hi!'); }); it('passes the context to the children', () => { const component = createComponent .withContext({ value: 'context' }) (); expect(component.findByQuery('span:contains(context)').length) .toEqual(3); }); it('does not leak the context across calls', () => { createComponent.withContext({ value: 'context' })(); const component = createComponent(); expect(component.findByQuery('span:contains(no-context)').length) .toEqual(3); }); it('passes the context to the children when using shallow render', () => { const component = createComponent .withContext({ value: 'context' }) .shallow(); expect(component.findByQuery('span:contains(context)').length) .toEqual(0); expect(component.findByQuery('span:contains(no-context)').length) .toEqual(0); }); it('passes the context to the children when using interleaved render', () => { const component = createComponent .withContext({ value: 'context' }) .interleaved(); expect(component.findByQuery('span:contains(context)').length) .toEqual(3); }); }); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "commonjs", "outDir": "./dist", "sourceMap": false, "noImplicitAny": true, "strictNullChecks": true }, "filesGlob": [ "./src/**/*.ts" ], "atom": { "rewriteTsconfig": true }, "files": [ "./src/add-ons/debug.ts", "./src/add-ons/exclude.ts", "./src/add-ons/index.ts", "./src/add-ons/mock.ts", "./src/add-ons/with-context.ts", "./src/defs.d.ts", "./src/pipeline.ts", "./src/react-unit.ts", "./src/render-instance.ts", "./src/resolver.ts", "./src/sizzle-bundle.d.ts", "./src/types.ts", "./src/unit-component.ts", "./src/utils.ts", "./src/wrapper.ts" ] } ================================================ FILE: tslint.json ================================================ { "extends": "tslint:recommended", "rules": { "curly": false, "interface-name": [true, "never-prefix"], "max-line-length": [true, 80], "member-access": null, "no-bitwise": false, "no-console": false, "no-shadowed-variable": false, "no-unused-variable": [true, "react", {"ignore-pattern": "^_$"}], "object-literal-sort-keys": false, "one-line": [true, "check-catch", "check-finally", "check-open-brace", "check-whitespace" ], "quotemark": [true, "single", "avoid-escape", "jsx-double"], "radix": false, "semicolon": [true, "always", "ignore-interfaces"], "switch-default": false, "trailing-comma": false, "triple-equals": false, "typedef-whitespace": [true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" }, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "space", "variable-declaration": "nospace" } ], "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-module", "check-separator", "check-typecast" ] } } ================================================ FILE: typings.json ================================================ { "dependencies": { "ramda": "registry:npm/ramda#0.21.0+20160531162943", "react": "registry:npm/react#15.0.1+20160601175240" }, "globalDependencies": { "react-addons-test-utils": "registry:dt/react-addons-test-utils#0.14.0+20160427035638" } }