Add

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Header>
  <!-- The Security element should be removed if WS-Security is not enabled on the SOAP target-url -->
  <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
   <wsse:UsernameToken>
    <wsse:Username>string</wsse:Username>
    <wsse:Password>string</wsse:Password>
    <wsse:Nonce EncodingType="string">string</wsse:Nonce>
    <wsu:Created>string</wsu:Created>
   </wsse:UsernameToken>
   <wsu:Timestamp wsu:Id="string">
    <wsu:Created>string</wsu:Created>
    <wsu:Expires>string</wsu:Expires>
   </wsu:Timestamp>
  </wsse:Security>
 </soapenv:Header>
 <soapenv:Body>
  <tns:Add xmlns:tns="http://tempuri.org/"><!-- mandatory -->
   <tns:intA><!-- mandatory -->3</tns:intA>
   <tns:intB><!-- mandatory -->3</tns:intB>
  </tns:Add>
 </soapenv:Body>
</soapenv:Envelope>


export interface CommitToGitRepoResult {

import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';

import { getDataDirectory } from '../common/electron-helpers';

export async function createPlugin(
  moduleName: string,
  version: string,
  mainJs: string,
) {
  const pluginDir = path.join(getDataDirectory(), 'plugins', moduleName);

  if (fs.existsSync(pluginDir)) {
    throw new Error(`Plugin already exists at "${pluginDir}"`);
  }

  mkdirp.sync(pluginDir);
  // Write package.json
  fs.writeFileSync(
    path.join(pluginDir, 'package.json'),
    JSON.stringify(
      {
        name: moduleName,
        version,
        private: true,
        insomnia: {
          name: moduleName.replace(/^insomnia-plugin-/, ''),
          description: '',
        },
        main: 'main.js',
      },
      null,
      2,
    ),
  );
  // Write main JS file
  fs.writeFileSync(path.join(pluginDir, 'main.js'), mainJs);
}


await pullBackendProject({ vcs: newVCS, backendProject, remoteProjects });

export interface RemoteCollectionsLoaderData {

import type { IRuleResult } from '@stoplight/spectral-core';
import { generate, runTests, Test } from 'insomnia-testing';
import path from 'path';
import { ActionFunction, redirect } from 'react-router-dom';

import * as session from '../../account/session';
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../common/constants';
import { database } from '../../common/database';
import { importResources, scanResources } from '../../common/import';
import * as models from '../../models';
import * as workspaceOperations from '../../models/helpers/workspace-operations';
import { DEFAULT_ORGANIZATION_ID } from '../../models/organization';
import { DEFAULT_PROJECT_ID, isRemoteProject } from '../../models/project';
import { UnitTest } from '../../models/unit-test';
import { isCollection } from '../../models/workspace';
import { getSendRequestCallback } from '../../network/unit-test-feature';
import { initializeLocalBackendProjectAndMarkForSync } from '../../sync/vcs/initialize-backend-project';
import { getVCS } from '../../sync/vcs/vcs';
import { invariant } from '../../utils/invariant';
import { SegmentEvent, trackSegmentEvent } from '../analytics';

// Project
export const createNewProjectAction: ActionFunction = async ({ request, params }) => {
  const { organizationId } = params;
  invariant(organizationId, 'Organization ID is required');
  const formData = await request.formData();
  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');
  const project = await models.project.create({ name });
  trackSegmentEvent(SegmentEvent.projectLocalCreate);
  return redirect(`/organization/${organizationId}/project/${project._id}`);
};

export const renameProjectAction: ActionFunction = async ({
  request,
  params,
}) => {
  const formData = await request.formData();

  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');

  const { projectId } = params;
  invariant(projectId, 'Project ID is required');

  const project = await models.project.getById(projectId);

  invariant(project, 'Project not found');

  invariant(
    !isRemoteProject(project),
    'Cannot rename remote project',
  );

  await models.project.update(project, { name });
};

export const deleteProjectAction: ActionFunction = async ({ params }) => {
  const { organizationId, projectId } = params;
  invariant(organizationId, 'Organization ID is required');
  invariant(projectId, 'Project ID is required');
  const project = await models.project.getById(projectId);
  invariant(project, 'Project not found');

  await models.stats.incrementDeletedRequestsForDescendents(project);
  await models.project.remove(project);

  trackSegmentEvent(SegmentEvent.projectLocalDelete);

  return redirect(`/organization/${DEFAULT_ORGANIZATION_ID}/project/${DEFAULT_PROJECT_ID}`);
};

// Workspace
export const createNewWorkspaceAction: ActionFunction = async ({
  params,
  request,
}) => {
  const { organizationId, projectId } = params;
  invariant(organizationId, 'Organization ID is required');
  invariant(projectId, 'Project ID is required');

  const project = await models.project.getById(projectId);

  invariant(project, 'Project not found');

  const formData = await request.formData();

  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');

  const scope = formData.get('scope');
  invariant(scope === 'design' || scope === 'collection', 'Scope is required');

  const flushId = await database.bufferChanges();

  const workspace = await models.workspace.create({
    name,
    scope,
    parentId: projectId,
  });

  // Create default env, cookie jar, and meta
  await models.environment.getOrCreateForParentId(workspace._id);
  await models.cookieJar.getOrCreateForParentId(workspace._id);
  await models.workspaceMeta.getOrCreateByParentId(workspace._id);

  await database.flushChanges(flushId);
  if (session.isLoggedIn() && isRemoteProject(project) && isCollection(workspace)) {
    const vcs = getVCS();
    if (vcs) {
      initializeLocalBackendProjectAndMarkForSync({
        vcs,
        workspace,
      });
    }
  }

  trackSegmentEvent(
    isCollection(workspace)
      ? SegmentEvent.collectionCreate
      : SegmentEvent.documentCreate
  );

  return redirect(
    `/organization/${organizationId}/project/${projectId}/workspace/${workspace._id}/${
      workspace.scope === 'collection' ? ACTIVITY_DEBUG : ACTIVITY_SPEC
    }`
  );
};

export const deleteWorkspaceAction: ActionFunction = async ({
  params,
  request,
}) => {
  const { organizationId, projectId } = params;
  invariant(projectId, 'projectId is required');

  const project = await models.project.getById(projectId);
  invariant(project, 'Project not found');

  const formData = await request.formData();

  const workspaceId = formData.get('workspaceId');
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');

  const workspace = await models.workspace.getById(workspaceId);
  invariant(workspace, 'Workspace not found');

  await models.stats.incrementDeletedRequestsForDescendents(workspace);
  await models.workspace.remove(workspace);

  try {
    const vcs = getVCS();
    if (vcs) {
      const backendProject = await vcs._getBackendProjectByRootDocument(workspace._id);
      await vcs._removeProject(backendProject);

      console.log({ projectsLOCAL: await vcs.localBackendProjects() });
    }
  } catch (err) {
    console.warn('Failed to remove project from VCS', err);
  }

  return redirect(`/organization/${organizationId}/project/${projectId}`);
};

export const duplicateWorkspaceAction: ActionFunction = async ({ request, params }) => {
  const { organizationId } = params;
  invariant(organizationId, 'Organization Id is required');
  const formData = await request.formData();
  const projectId = formData.get('projectId');
  invariant(typeof projectId === 'string', 'Project ID is required');

  const workspaceId = formData.get('workspaceId');
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');

  const workspace = await models.workspace.getById(workspaceId);
  invariant(workspace, 'Workspace not found');

  const name = formData.get('name') || '';
  invariant(typeof name === 'string', 'Name is required');

  const duplicateToProject = await models.project.getById(projectId);
  invariant(duplicateToProject, 'Project not found');

  const newWorkspace = await workspaceOperations.duplicate(workspace, {
    name,
    parentId: projectId,
  });

  // Create default env, cookie jar, and meta
  await models.environment.getOrCreateForParentId(newWorkspace._id);
  await models.cookieJar.getOrCreateForParentId(newWorkspace._id);
  await models.workspaceMeta.getOrCreateByParentId(newWorkspace._id);

  try {
    // Mark for sync if logged in and in the expected project
    const vcs = getVCS();
    if (session.isLoggedIn() && vcs && isRemoteProject(duplicateToProject)) {
      await initializeLocalBackendProjectAndMarkForSync({
        vcs: vcs.newInstance(),
        workspace: newWorkspace,
      });
    }
  } catch (e) {
    console.warn('Failed to initialize local backend project', e);
  }

  return redirect(
    `/organization/${organizationId}/project/${projectId}/workspace/${newWorkspace._id}/${
      newWorkspace.scope === 'collection' ? ACTIVITY_DEBUG : ACTIVITY_SPEC
    }`
  );
};

export const updateWorkspaceAction: ActionFunction = async ({ request }) => {
  const formData = await request.formData();
  const workspaceId = formData.get('workspaceId');
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');

  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');

  const description = formData.get('description');
  if (description) {
    invariant(typeof description === 'string', 'Description is required');
  }

  const workspace = await models.workspace.getById(workspaceId);
  invariant(workspace, 'Workspace not found');

  if (workspace.scope === 'design') {
    const apiSpec = await models.apiSpec.getByParentId(workspaceId);
    invariant(apiSpec, 'No Api Spec found for this workspace');

    await models.apiSpec.update(apiSpec, {
      fileName: name,
    });
  }

  await models.workspace.update(workspace, {
    name,
    description: description || workspace.description,
  });
};

// Test Suite
export const createNewTestSuiteAction: ActionFunction = async ({
  request,
  params,
}) => {
  const { organizationId, workspaceId, projectId } = params;
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');
  const formData = await request.formData();
  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');

  const unitTestSuite = await models.unitTestSuite.create({
    parentId: workspaceId,
    name,
  });

  trackSegmentEvent(SegmentEvent.testSuiteCreate);

  return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/test/test-suite/${unitTestSuite._id}`);
};

export const deleteTestSuiteAction: ActionFunction = async ({ params }) => {
  const { organizationId, workspaceId, projectId, testSuiteId } = params;
  invariant(typeof testSuiteId === 'string', 'Test Suite ID is required');
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');
  invariant(typeof projectId === 'string', 'Project ID is required');

  const unitTestSuite = await models.unitTestSuite.getById(testSuiteId);

  invariant(unitTestSuite, 'Test Suite not found');

  await models.unitTestSuite.remove(unitTestSuite);

  trackSegmentEvent(SegmentEvent.testSuiteDelete);

  return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/test`);
};

export const runAllTestsAction: ActionFunction = async ({
  params,
}) => {
  const { organizationId, projectId, workspaceId, testSuiteId } = params;
  invariant(typeof projectId === 'string', 'Project ID is required');
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');
  invariant(typeof testSuiteId === 'string', 'Test Suite ID is required');

  const unitTests = await database.find<UnitTest>(models.unitTest.type, {
    parentId: testSuiteId,
  });
  invariant(unitTests, 'No unit tests found');
  console.log('unitTests', unitTests);

  const tests: Test[] = unitTests
    .filter(t => t !== null)
    .map(t => ({
      name: t.name,
      code: t.code,
      defaultRequestId: t.requestId,
    }));

  const src = generate([{ name: 'My Suite', suites: [], tests }]);

  const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(
    workspaceId
  );

  const sendRequest = getSendRequestCallback(
    workspaceMeta?.activeEnvironmentId || undefined
  );

  const results = await runTests(src, { sendRequest });

  const testResult = await models.unitTestResult.create({
    results,
    parentId: workspaceId,
  });

  trackSegmentEvent(SegmentEvent.unitTestRun);

  return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/test/test-suite/${testSuiteId}/test-result/${testResult._id}`);
};

export const renameTestSuiteAction: ActionFunction = async ({ request, params }) => {
  const { workspaceId, projectId, testSuiteId } = params;
  invariant(typeof testSuiteId === 'string', 'Test Suite ID is required');
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');
  invariant(typeof projectId === 'string', 'Project ID is required');

  const formData = await request.formData();
  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');

  const unitTestSuite = await database.getWhere(models.unitTestSuite.type, {
    _id: testSuiteId,
  });

  invariant(unitTestSuite, 'Test Suite not found');

  await models.unitTestSuite.update(unitTestSuite, { name });
};

// Unit Test
export const createNewTestAction: ActionFunction = async ({ request, params }) => {
  const { testSuiteId } = params;
  invariant(typeof testSuiteId === 'string', 'Test Suite ID is required');
  const formData = await request.formData();

  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');

  await models.unitTest.create({
    parentId: testSuiteId,
    code: `const response1 = await insomnia.send();
expect(response1.status).to.equal(200);`,
    name,
  });

  trackSegmentEvent(SegmentEvent.unitTestCreate);
};

export const deleteTestAction: ActionFunction = async ({ params }) => {
  const { testId } = params;
  invariant(typeof testId === 'string', 'Test ID is required');

  const unitTest = await database.getWhere<UnitTest>(models.unitTest.type, {
    _id: testId,
  });

  invariant(unitTest, 'Test not found');

  await models.unitTest.remove(unitTest);
  trackSegmentEvent(SegmentEvent.unitTestDelete);
};

export const updateTestAction: ActionFunction = async ({ request, params }) => {
  const { testId } = params;
  const formData = await request.formData();
  invariant(typeof testId === 'string', 'Test ID is required');
  const code = formData.get('code');
  invariant(typeof code === 'string', 'Code is required');
  const name = formData.get('name');
  invariant(typeof name === 'string', 'Name is required');
  const requestId = formData.get('requestId');

  if (requestId) {
    invariant(typeof requestId === 'string', 'Request ID is required');
  }

  const unitTest = await database.getWhere<UnitTest>(models.unitTest.type, {
    _id: testId,
  });
  invariant(unitTest, 'Test not found');

  await models.unitTest.update(unitTest, { name, code, requestId: requestId || null });
};

export const runTestAction: ActionFunction = async ({ params }) => {
  const { organizationId, projectId, workspaceId, testSuiteId, testId } = params;
  invariant(typeof testId === 'string', 'Test ID is required');

  const unitTest = await database.getWhere<UnitTest>(models.unitTest.type, {
    _id: testId,
  });
  invariant(unitTest, 'Test not found');

  const tests: Test[] = [
    {
      name: unitTest.name,
      code: unitTest.code,
      defaultRequestId: unitTest.requestId,
    },
  ];
  const src = generate([{ name: 'My Suite', suites: [], tests }]);
  const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(
    unitTest.parentId
  );

  const sendRequest = getSendRequestCallback(
    workspaceMeta?.activeEnvironmentId || undefined
  );

  const results = await runTests(src, { sendRequest });

  const testResult = await models.unitTestResult.create({
    results,
    parentId: unitTest.parentId,
  });

  trackSegmentEvent(SegmentEvent.unitTestRun);

  return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/test/test-suite/${testSuiteId}/test-result/${testResult._id}`);
};

// Api Spec
export const updateApiSpecAction: ActionFunction = async ({
  request,
  params,
}) => {
  const { workspaceId } = params;
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');
  const formData = await request.formData();
  const contents = formData.get('contents');
  const fromSync = Boolean(formData.get('fromSync'));

  invariant(typeof contents === 'string', 'Contents is required');

  const apiSpec = await models.apiSpec.getByParentId(workspaceId);

  invariant(apiSpec, 'API Spec not found');
  await database.update({
    ...apiSpec,
    modified: Date.now(),
    created: fromSync ? Date.now() : apiSpec.created,
    contents,
  }, fromSync);
};

export const generateCollectionFromApiSpecAction: ActionFunction = async ({
  params,
}) => {
  const { organizationId, projectId, workspaceId } = params;

  invariant(typeof projectId === 'string', 'Project ID is required');
  invariant(typeof workspaceId === 'string', 'Workspace ID is required');

  const apiSpec = await models.apiSpec.getByParentId(workspaceId);

  if (!apiSpec) {
    throw new Error('No API Specification was found');
  }

  const workspace = await models.workspace.getById(workspaceId);

  invariant(workspace, 'Workspace not found');

  const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspaceId);

  const isLintError = (result: IRuleResult) => result.severity === 0;
  const rulesetPath = path.join(
    process.env['INSOMNIA_DATA_PATH'] || window.app.getPath('userData'),
    `version-control/git/${workspaceMeta?.gitRepositoryId}/other/.spectral.yaml`,
  );

  const results = (await window.main.spectralRun({ contents: apiSpec.contents, rulesetPath })).filter(isLintError);
  if (apiSpec.contents && results && results.length) {
    throw new Error('Error Generating Configuration');
  }

  await scanResources({
    content: apiSpec.contents,
  });

  await importResources({
    projectId,
    workspaceId,
  });

  return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/${ACTIVITY_DEBUG}`);
};


import { expect } from '@playwright/test';

import { loadFixture } from '../../playwright/paths';
import { test } from '../../playwright/test';

test('can make websocket connection', async ({ app, page }) => {
  test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
  const statusTag = page.locator('[data-testid="response-status-tag"]:visible');
  const responseBody = page.locator('[data-testid="response-pane"] >> [data-testid="CodeEditor"]:visible', {
    has: page.locator('.CodeMirror-activeline'),
  });

  await page.getByTestId('project').click();
  await page.getByRole('button', { name: 'Create' }).click();

  const text = await loadFixture('websockets.yaml');
  await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);

  await page.getByRole('menuitem', { name: 'Import' }).click();
  await page.getByText('Clipboard').click();
  await page.getByRole('button', { name: 'Scan' }).click();
  await page.getByRole('button', { name: 'Import' }).click();

  await page.getByRole('link', { name: 'Debug' }).click();

  await page.getByRole('button', { name: 'localhost:4010' }).click();
  await expect(page.locator('.app')).toContainText('ws://localhost:4010');
  await page.click('text=Connect');
  await expect(statusTag).toContainText('101 Switching Protocols');
  await page.getByRole('tab', { name: 'Timeline' }).click();
  await expect(responseBody).toContainText('WebSocket connection established');
  await page.click('text=Disconnect');
  await expect(responseBody).toContainText('Closing connection with code 1005');

  // Can connect with Basic Auth
  await page.getByRole('button', { name: 'basic-auth' }).click();
  await expect(page.locator('.app')).toContainText('ws://localhost:4010/basic-auth');
  await page.click('text=Connect');
  await expect(statusTag).toContainText('101 Switching Protocols');
  await page.getByRole('tab', { name: 'Timeline' }).click();
  await expect(responseBody).toContainText('> authorization: Basic dXNlcjpwYXNzd29yZA==');

  // Can connect with Bearer Auth
  await page.getByRole('button', { name: 'bearer' }).click();
  await expect(page.locator('.app')).toContainText('ws://localhost:4010/bearer');
  await page.click('text=Connect');
  await expect(statusTag).toContainText('101 Switching Protocols');
  await page.getByRole('tab', { name: 'Timeline' }).click();
  await expect(responseBody).toContainText('> authorization: Bearer insomnia-cool-token-!!!1112113243111');

  // Can handle redirects
  await page.getByRole('button', { name: 'redirect' }).click();
  await expect(page.locator('.app')).toContainText('ws://localhost:4010/redirect');
  await page.click('text=Connect');
  await expect(statusTag).toContainText('101 Switching Protocols');
  await page.getByRole('tab', { name: 'Timeline' }).click();
  await expect(responseBody).toContainText('WebSocket connection established');

  const webSocketActiveConnections = page.locator('[data-testid="WebSocketSpinner__Connected"]');

  // Basic auth, Bearer auth, and Redirect connections are displayed as open
  await expect(webSocketActiveConnections).toHaveCount(3);

  // Can disconnect from all connections
  await page.locator('button[name="DisconnectDropdown__DropdownButton"]').click();
  await page.getByRole('menuitem', { name: 'Disconnect all requests' }).click();
  await expect(webSocketActiveConnections).toHaveCount(0);
});


this.prevState = this.prevProps = this.prevContext = this.base = this._parentComponent = this._component = null;

this.state = hook(this, 'getInitialState') || {};

keep keep keep keep replace keep keep keep keep

import { EMPTY_BASE } from '../constants';
import { getNodeProps } from '.';
import { isFunction } from '../util';

/** Check if a VNode is a reference to a stateless functional component.
 *	A function component is represented as a VNode whose `nodeName` property is a reference to a function.
 *	If that function is not a Component (ie, has no `.render()` method on a prototype), it is considered a stateless functional component.
 *	@param {VNode} vnode	A VNode
 *	@private
 */
export function isFunctionalComponent({ nodeName }) {
	return isFunction(nodeName) && !nodeName.prototype.render;
}

/** Construct a resultant VNode from a VNode referencing a stateless functional component.
 *	@param {VNode} vnode	A VNode with a `nodeName` property that is a reference to a function.
 *	@private
 */
export function buildFunctionalComponent(vnode, context) {
	return vnode.nodeName(getNodeProps(vnode), context) || EMPTY_BASE;
}


.babelrc
.eslintrc
.travis.yml
karma.conf.js
rollup.config.js
test/


import { empty, isString, isFunction, delve } from './util';

/** Create an Event handler function that sets a given state property.
 *	@param {Component} component	The component whose state should be updated
 *	@param {string} key				A dot-notated key path to update in the component's state
 *	@param {string} eventPath		A dot-notated key path to the value that should be retrieved from the Event or component
 *	@returns {function} linkedStateHandler
 *	@private
 */
export function createLinkedState(component, key, eventPath) {
	let path = key.split('.'),
		p0 = path[0],
		len = path.length;
	return function(e) {
		let t = this,
			s = component.state,
			obj = s,
			v, i;
		if (isString(eventPath)) {
			v = delve(e, eventPath);
			if (empty(v) && (t=t._component)) {
				v = delve(t, eventPath);
			}
		}
		else {
			v = (t.nodeName+t.type).match(/^input(checkbox|radio)$/i) ? t.checked : t.value;
		}
		if (isFunction(v)) v = v.call(t);
		if (len>1) {
			for (i=0; i<len-1; i++) {
				obj = obj[path[i]] || (obj[path[i]] = {});
			}
			obj[path[i]] = v;
			v = s[p0];
		}
		component.setState({ [p0]: v });
	};
}


/** Accepts `props` and `state`, and returns a new Virtual DOM tree to build.

*	Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx).

*	@param {object} props		Props (eg: JSX attributes) received from parent element/component

*	@param {object} state		The component's current state

keep keep keep keep add keep keep keep keep keep

*	@param {object} context		Context object (if a parent component has provided context)


import { h, render, rerender, Component } from '../../src/preact';
/** @jsx h */

describe('render()', () => {
	let scratch;

	before( () => {
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);
	});

	beforeEach( () => {
		scratch.innerHTML = '';
	});

	after( () => {
		scratch.parentNode.removeChild(scratch);
		scratch = null;
	});

	it('should create empty nodes (<* />)', () => {
		render(
, scratch);
		expect(scratch.childNodes)
			.to.have.length(1)
			.and.to.have.deep.property('0.nodeName', 'DIV');

		scratch.innerHTML = '';

		render(, scratch);
		expect(scratch.childNodes)
			.to.have.length(1)
			.and.to.have.deep.property('0.nodeName', 'SPAN');

		scratch.innerHTML = '';

		render(<foo />, scratch);
		render(<x-bar />, scratch);
		expect(scratch.childNodes).to.have.length(2);
		expect(scratch.childNodes[0]).to.have.property('nodeName', 'FOO');
		expect(scratch.childNodes[1]).to.have.property('nodeName', 'X-BAR');
	});

	it('should nest empty nodes', () => {
		render((

				<foo />
				<x-bar />

		), scratch);

		expect(scratch.childNodes)
			.to.have.length(1)
			.and.to.have.deep.property('0.nodeName', 'DIV');

		let c = scratch.childNodes[0].childNodes;
		expect(c).to.have.length(3);
		expect(c).to.have.deep.property('0.nodeName', 'SPAN');
		expect(c).to.have.deep.property('1.nodeName', 'FOO');
		expect(c).to.have.deep.property('2.nodeName', 'X-BAR');
	});

	it('should not render falsey values', () => {
		render((

				{null},{undefined},{false},{0},{NaN}

		), scratch);

		expect(scratch.firstChild).to.have.property('innerHTML', ',,,0,NaN');
	});

	it('should clear falsey attributes', () => {
		let root = render((

		), scratch);

		root = render((

		), scratch, root);

		expect(scratch).to.have.property('innerHTML', '

');
	});

	it('should clear falsey DOM properties', () => {
		let root;
		function test(val) {
			root = render((

					<input value={val} />

			), scratch, root);
		}

		test('2');
		test(false);
		expect(scratch).to.have.property('innerHTML', '
<input>

', 'for false');

		test('3');
		test(null);
		expect(scratch).to.have.property('innerHTML', '
<input>

', 'for null');

		test('4');
		test(undefined);
		expect(scratch).to.have.property('innerHTML', '
<input>

', 'for undefined');
	});

	it('should apply string attributes', () => {
		render(
, scratch);

		let div = scratch.childNodes[0];
		expect(div).to.have.deep.property('attributes.length', 2);

		expect(div).to.have.deep.property('attributes[0].name', 'foo');
		expect(div).to.have.deep.property('attributes[0].value', 'bar');

		expect(div).to.have.deep.property('attributes[1].name', 'data-foo');
		expect(div).to.have.deep.property('attributes[1].value', 'databar');
	});

	it('should apply class as String', () => {
		render(
, scratch);
		expect(scratch.childNodes[0]).to.have.property('className', 'foo');
	});

	it('should alias className to class', () => {
		render(
, scratch);
		expect(scratch.childNodes[0]).to.have.property('className', 'bar');
	});

	it('should apply style as String', () => {
		render(
, scratch);
		expect(scratch.childNodes[0]).to.have.deep.property('style.cssText')
			.that.matches(/top\s*:\s*5px\s*/)
			.and.matches(/position\s*:\s*relative\s*/);
	});

	it('should only register on* functions as handlers', () => {
		let click = () => {},
			onclick = () => {};

		let proto = document.createElement('div').constructor.prototype;

		sinon.spy(proto, 'addEventListener');

		render(
, scratch);

		expect(scratch.childNodes[0]).to.have.deep.property('attributes.length', 0);

		expect(proto.addEventListener).to.have.been.calledOnce
			.and.to.have.been.calledWithExactly('click', sinon.match.func);

		proto.addEventListener.restore();
	});

	it('should add and remove event handlers', () => {
		let click = sinon.spy(),
			mousedown = sinon.spy();

		let proto = document.createElement('div').constructor.prototype;
		sinon.spy(proto, 'addEventListener');
		sinon.spy(proto, 'removeEventListener');

		function fireEvent(on, type) {
			let e = document.createEvent('Event');
			e.initEvent(type);
			on.dispatchEvent(e);
		}

		render(
 click(1) } onMouseDown={ mousedown } />, scratch);

		expect(proto.addEventListener).to.have.been.calledTwice
			.and.to.have.been.calledWith('click')
			.and.calledWith('mousedown');

		fireEvent(scratch.childNodes[0], 'click');
		expect(click).to.have.been.calledOnce
			.and.calledWith(1);

		proto.addEventListener.reset();
		click.reset();

		render(
 click(2) } />, scratch, scratch.firstChild);

		expect(proto.addEventListener).not.to.have.been.called;

		expect(proto.removeEventListener)
			.to.have.been.calledOnce
			.and.calledWith('mousedown');

		fireEvent(scratch.childNodes[0], 'click');
		expect(click).to.have.been.calledOnce
			.and.to.have.been.calledWith(2);

		fireEvent(scratch.childNodes[0], 'mousedown');
		expect(mousedown).not.to.have.been.called;

		proto.removeEventListener.reset();
		click.reset();
		mousedown.reset();

		render(
, scratch, scratch.firstChild);

		expect(proto.removeEventListener)
			.to.have.been.calledOnce
			.and.calledWith('click');

		fireEvent(scratch.childNodes[0], 'click');
		expect(click).not.to.have.been.called;

		proto.addEventListener.restore();
		proto.removeEventListener.restore();
	});

	it('should serialize style objects', () => {
		render(
, scratch);

		let { style } = scratch.childNodes[0];
		expect(style).to.have.property('color', 'rgb(255, 255, 255)');
		expect(style).to.have.property('background').that.contains('rgb(255, 100, 0)');
		expect(style).to.have.property('backgroundPosition').that.matches(/0(px)? 0(px)?/);
		expect(style).to.have.property('backgroundSize', 'cover');
		expect(style).to.have.property('padding', '5px');
		expect(style).to.have.property('top', '100px');
		expect(style).to.have.property('left', '100%');
	});

	it('should serialize class/className', () => {
		render(
, scratch);

		let { className } = scratch.childNodes[0];
		expect(className).to.be.a.string;
		expect(className.split(' '))
			.to.include.members(['yes1', 'yes2', 'yes3', 'yes4', 'yes5'])
			.and.not.include.members(['no1', 'no2', 'no3', 'no4', 'no5']);
	});

	it('should support dangerouslySetInnerHTML', () => {
		let html = 'foo & bar';
		let root = render(
, scratch);

		expect(scratch.firstChild).to.have.property('innerHTML', html);
		expect(scratch.innerHTML).to.equal('
'+html+'
');

		root = render(
ab
, scratch, root);

		expect(scratch).to.have.property('innerHTML', `<div>a<strong>b</strong></div>`);

		root = render(
, scratch, root);

		expect(scratch.innerHTML).to.equal('
'+html+'
');
	});

	it('should reconcile mutated DOM attributes', () => {
		let check = p => render(<input type="checkbox" checked={p} />, scratch, scratch.lastChild),
			value = () => scratch.lastChild.checked,
			setValue = p => scratch.lastChild.checked = p;
		check(true);
		expect(value()).to.equal(true);
		check(false);
		expect(value()).to.equal(false);
		check(true);
		expect(value()).to.equal(true);
		setValue(true);
		check(false);
		expect(value()).to.equal(false);
		setValue(false);
		check(true);
		expect(value()).to.equal(true);
	});

	it('should render components', () => {
		class C1 extends Component {
			render() {
				return
C1
;
			}
		}
		sinon.spy(C1.prototype, 'render');
		render(<C1 />, scratch);

		expect(C1.prototype.render)
			.to.have.been.calledOnce
			.and.to.have.been.calledWithMatch({}, {})
			.and.to.have.returned(sinon.match({ nodeName:'div' }));

		expect(scratch.innerHTML).to.equal('
C1
');
	});

	it('should render components with props', () => {
		const PROPS = { foo:'bar', onBaz:()=>{} };
		let constructorProps;

		class C2 extends Component {
			constructor(props) {
				super(props);
				constructorProps = props;
			}
			render(props) {
				return
;
			}
		}
		sinon.spy(C2.prototype, 'render');

		render(<C2 {...PROPS} />, scratch);

		expect(constructorProps).to.deep.equal(PROPS);

		expect(C2.prototype.render)
			.to.have.been.calledOnce
			.and.to.have.been.calledWithMatch(PROPS, {})
			.and.to.have.returned(sinon.match({
				nodeName: 'div',
				attributes: PROPS
			}));

		expect(scratch.innerHTML).to.equal('

');
	});

	it('should render functional components', () => {
		const PROPS = { foo:'bar', onBaz:()=>{} };

		const C3 = sinon.spy( props =>
 );

		render(<C3 {...PROPS} />, scratch);

		expect(C3)
			.to.have.been.calledOnce
			.and.to.have.been.calledWith(PROPS)
			.and.to.have.returned(sinon.match({
				nodeName: 'div',
				attributes: PROPS
			}));

		expect(scratch.innerHTML).to.equal('

');
	});

	it('should render nested functional components', () => {
		const PROPS = { foo:'bar', onBaz:()=>{} };

		const Outer = sinon.spy(
			props => <Inner {...props} />
		);

		const Inner = sinon.spy(
			props =>
inner

		);

		render(<Outer {...PROPS} />, scratch);

		expect(Outer)
			.to.have.been.calledOnce
			.and.to.have.been.calledWith(PROPS)
			.and.to.have.returned(sinon.match({
				nodeName: Inner,
				attributes: PROPS
			}));

		expect(Inner)
			.to.have.been.calledOnce
			.and.to.have.been.calledWith(PROPS)
			.and.to.have.returned(sinon.match({
				nodeName: 'div',
				attributes: PROPS,
				children: ['inner']
			}));

		expect(scratch.innerHTML).to.equal('
inner
');
	});

	it('should re-render nested functional components', () => {
		let doRender = null;
		class Outer extends Component {
			componentDidMount() {
				let i = 1;
				doRender = () => this.setState({ i: ++i });
			}
			componentWillUnmount() {}
			render(props, { i }) {
				return <Inner i={i} {...props} />;
			}
		}
		sinon.spy(Outer.prototype, 'render');
		sinon.spy(Outer.prototype, 'componentWillUnmount');

		let j = 0;
		const Inner = sinon.spy(
			props =>
inner

		);

		render(<Outer foo="bar" />, scratch);

		// update & flush
		doRender();
		rerender();

		expect(Outer.prototype.componentWillUnmount)
			.not.to.have.been.called;

		expect(Inner).to.have.been.calledTwice;

		expect(Inner.secondCall)
			.to.have.been.calledWith({ foo:'bar', i:2 })
			.and.to.have.returned(sinon.match({
				attributes: {
					j: 2,
					i: 2,
					foo: 'bar'
				}
			}));

		expect(scratch.innerHTML).to.equal('
inner
');

		// update & flush
		doRender();
		rerender();

		expect(Inner).to.have.been.calledThrice;

		expect(Inner.thirdCall)
			.to.have.been.calledWith({ foo:'bar', i:3 })
			.and.to.have.returned(sinon.match({
				attributes: {
					j: 3,
					i: 3,
					foo: 'bar'
				}
			}));

		expect(scratch.innerHTML).to.equal('
inner
');
	});

	it('should re-render nested components', () => {
		let doRender = null,
			alt = false;

		class Outer extends Component {
			componentDidMount() {
				let i = 1;
				doRender = () => this.setState({ i: ++i });
			}
			componentWillUnmount() {}
			render(props, { i }) {
				if (alt) return
;
				return <Inner i={i} {...props} />;
			}
		}
		sinon.spy(Outer.prototype, 'render');
		sinon.spy(Outer.prototype, 'componentDidMount');
		sinon.spy(Outer.prototype, 'componentWillUnmount');

		let j = 0;
		class Inner extends Component {
			constructor(...args) {
				super();
				this._constructor(...args);
			}
			_constructor() {}
			componentWillMount() {}
			componentDidMount() {}
			componentWillUnmount() {}
			componentDidUnmount() {}
			render(props) {
				return
inner
;
			}
		}
		sinon.spy(Inner.prototype, '_constructor');
		sinon.spy(Inner.prototype, 'render');
		sinon.spy(Inner.prototype, 'componentWillMount');
		sinon.spy(Inner.prototype, 'componentDidMount');
		sinon.spy(Inner.prototype, 'componentDidUnmount');
		sinon.spy(Inner.prototype, 'componentWillUnmount');

		render(<Outer foo="bar" />, scratch);

		expect(Outer.prototype.componentDidMount).to.have.been.calledOnce;

		// update & flush
		doRender();
		rerender();

		expect(Outer.prototype.componentWillUnmount).not.to.have.been.called;

		expect(Inner.prototype._constructor).to.have.been.calledOnce;
		expect(Inner.prototype.componentWillUnmount).not.to.have.been.called;
		expect(Inner.prototype.componentDidUnmount).not.to.have.been.called;
		expect(Inner.prototype.componentWillMount).to.have.been.calledOnce;
		expect(Inner.prototype.componentDidMount).to.have.been.calledOnce;
		expect(Inner.prototype.render).to.have.been.calledTwice;

		expect(Inner.prototype.render.secondCall)
			.to.have.been.calledWith({ foo:'bar', i:2 })
			.and.to.have.returned(sinon.match({
				attributes: {
					j: 2,
					i: 2,
					foo: 'bar'
				}
			}));

		expect(scratch.innerHTML).to.equal('
inner
');

		// update & flush
		doRender();
		rerender();

		expect(Inner.prototype.componentWillUnmount).not.to.have.been.called;
		expect(Inner.prototype.componentDidUnmount).not.to.have.been.called;
		expect(Inner.prototype.componentWillMount).to.have.been.calledOnce;
		expect(Inner.prototype.componentDidMount).to.have.been.calledOnce;
		expect(Inner.prototype.render).to.have.been.calledThrice;

		expect(Inner.prototype.render.thirdCall)
			.to.have.been.calledWith({ foo:'bar', i:3 })
			.and.to.have.returned(sinon.match({
				attributes: {
					j: 3,
					i: 3,
					foo: 'bar'
				}
			}));

		expect(scratch.innerHTML).to.equal('
inner
');

		// update & flush
		alt = true;
		doRender();
		rerender();

		expect(Inner.prototype.componentWillUnmount).to.have.been.calledOnce;
		expect(Inner.prototype.componentDidUnmount).to.have.been.calledOnce;
	});

	// Test for Issue #73
	it('should remove orphaned elements replaced by Components', () => {
		class Comp extends Component {
			render() {
				return span in a component;
			}
		}

		let root;
		function test(content) {
			root = render(content, scratch, root);
		}

		test(<Comp />);
		test(
just a div
);
		test(<Comp />);

		expect(scratch.innerHTML).to.equal('span in a component');
	});
});


import { h, render, rerender, Component } from '../../src/preact';
/** @jsx h */

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

	before( () => {
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);
	});

	beforeEach( () => {
		scratch.innerHTML = '';
	});

	after( () => {
		scratch.parentNode.removeChild(scratch);
		scratch = null;
	});

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

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

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

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

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

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

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

			rerender();

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

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


import { clone, toLowerCase, isFunction, isString, hasOwnProperty } from '../util';
import { isFunctionalComponent } from './functional-component';
import { getNodeType } from '../dom/index';

/** Check if two nodes are equivalent.
 *	@param {Element} node
 *	@param {VNode} vnode
 *	@private
 */
export function isSameNodeType(node, vnode) {
	if (isFunctionalComponent(vnode)) return true;
	let nodeName = vnode.nodeName;
	if (isFunction(nodeName)) return node._componentConstructor===nodeName;
	if (getNodeType(node)===3) return isString(vnode);
	return toLowerCase(node.nodeName)===nodeName;
}

/**
 * Reconstruct Component-style `props` from a VNode.
 * Ensures default/fallback values from `defaultProps`:
 * Own-properties of `defaultProps` not present in `vnode.attributes` are added.
 * @param {VNode} vnode
 * @returns {Object} props
 */
export function getNodeProps(vnode) {
	let props = clone(vnode.attributes),
		c = vnode.children;
	if (c) props.children = c;

	let defaultProps = vnode.nodeName.defaultProps;
	if (defaultProps) {
		for (let i in defaultProps) {
			if (hasOwnProperty.call(defaultProps, i) && !(i in props)) {
				props[i] = defaultProps[i];
			}
		}
	}

	return props;
}


import { EMPTY_BASE } from '../constants';

import { EMPTY, EMPTY_BASE } from '../constants';

import { hook } from './hooks';
import { extend, clone, isFunction } from './util';
import { createLinkedState } from './linked-state';
import { triggerComponentRender, renderComponent } from './vdom/component';

/** Base Component class, for he ES6 Class method of creating Components
 *	@public
 *
 *	@example
 *	class MyFoo extends Component {
 *		render(props, state) {
 *			return <div />;
 *		}
 *	}
 */
export default function Component(props, context) {
	/** @private */
	this._dirty = this._disableRendering = false;
	/** @private */
	this._linkedStates = {};
	/** @private */
	this._renderCallbacks = [];
	/** @public */
	this.prevState = this.prevProps = this.prevContext = this.base = this._parentComponent = this._component = null;
	/** @public */
	this.context = context || null;
	/** @type {object} */
	this.props = props || {};
	/** @type {object} */
	this.state = hook(this, 'getInitialState') || {};
}

extend(Component.prototype, {

	/** Returns a `boolean` value indicating if the component should re-render when receiving the given `props` and `state`.
	 *	@param {object} nextProps
	 *	@param {object} nextState
	 *	@param {object} nextContext
	 *	@returns {Boolean} should the component re-render
	 *	@name shouldComponentUpdate
	 *	@function
	 */
	// shouldComponentUpdate() {
	// 	return true;
	// },

	/** Returns a function that sets a state property when called.
	 *	Calling linkState() repeatedly with the same arguments returns a cached link function.
	 *
	 *	Provides some built-in special cases:
	 *		- Checkboxes and radio buttons link their boolean `checked` value
	 *		- Inputs automatically link their `value` property
	 *		- Event paths fall back to any associated Component if not found on an element
	 *		- If linked value is a function, will invoke it and use the result
	 *
	 *	@param {string} key				The path to set - can be a dot-notated deep key
	 *	@param {string} [eventPath]		If set, attempts to find the new state value at a given dot-notated path within the object passed to the linkedState setter.
	 *	@returns {function} linkStateSetter(e)
	 *
	 *	@example Update a "text" state value when an input changes:
	 *		<input onChange={ this.linkState('text') } />
	 *
	 *	@example Set a deep state value on click
	 *		<button onClick={ this.linkState('touch.coords', 'touches.0') }>Tap</button
	 */
	linkState(key, eventPath) {
		let c = this._linkedStates,
			cacheKey = key + '|' + (eventPath || '');
		return c[cacheKey] || (c[cacheKey] = createLinkedState(this, key, eventPath));
	},

	/** Update component state by copying properties from `state` to `this.state`.
	 *	@param {object} state		A hash of state properties to update with new values
	 */
	setState(state, callback) {
		let s = this.state;
		if (!this.prevState) this.prevState = clone(s);
		extend(s, isFunction(state) ? state(s, this.props) : state);
		if (callback) this._renderCallbacks.push(callback);
		triggerComponentRender(this);
	},

	/** Immediately perform a synchronous re-render of the component.
	 *	@private
	 */
	forceUpdate() {
		renderComponent(this);
	},

	/** Accepts `props` and `state`, and returns a new Virtual DOM tree to build.
	 *	Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx).
	 *	@param {object} props		Props (eg: JSX attributes) received from parent element/component
	 *	@param {object} state		The component's current state
	 *	@returns VNode
	 */
	render() {
		return null;
	}

});


import { h } from '../../src/preact';
import VNode from '../../src/vnode';
import { expect } from 'chai';

/*eslint-env browser, mocha */

/** @jsx h */

let flatten = obj => JSON.parse(JSON.stringify(obj));

describe('h(jsx)', () => {
	it('should return a VNode', () => {
		let r;
		expect( () => r = h('foo') ).not.to.throw();
		expect(r).to.be.an('object');
		expect(r).to.be.an.instanceof(VNode);
		expect(r).to.have.property('nodeName', 'foo');
		expect(r).to.have.property('attributes', undefined);
		expect(r).to.have.property('children', undefined);
	});

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

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

		expect(r).to.be.an('object')
			.with.property('children')
			.that.deep.equals([
				new VNode('bar'),
				new VNode('baz')
			]);
	});

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

		r = flatten(r);

		expect(r).to.be.an('object')
			.with.property('children')
			.that.deep.equals([
				{ nodeName:'bar' },
				{ nodeName:'baz', children:[
					{ nodeName:'test' }
				]}
			]);
	});

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

		expect(r).to.be.an('object')
			.with.property('children')
			.with.length(1)
			.with.property('0')
			.that.equals('textstuff');
	});

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

		r = flatten(r);

		expect(r).to.be.an('object')
			.with.property('children')
			.that.deep.equals([
				'onetwo',
				{ nodeName:'bar' },
				'three',
				{ nodeName:'baz' },
				{ nodeName:'baz' },
				'fourfivesix'
			]);
	});
});


import { h, render, Component } from '../../src/preact';
/** @jsx h */

// gives call count and argument errors names (otherwise sinon just uses "spy"):
let spy = (name, ...args) => {
	let spy = sinon.spy(...args);
	spy.displayName = `spy('${name}')`;
	return spy;
};

describe('refs', () => {
	let scratch;

	before( () => {
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);
	});

	beforeEach( () => {
		scratch.innerHTML = '';
	});

	after( () => {
		scratch.parentNode.removeChild(scratch);
		scratch = null;
	});

	it('should invoke refs in render()', () => {
		let ref = spy('ref');
		render(
, scratch);
		expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild);
	});

	it('should invoke refs in Component.render()', () => {
		let outer = spy('outer'),
			inner = spy('inner');
		class Foo extends Component {
			render() {
				return (

				);
			}
		}
		render(<Foo />, scratch);

		expect(outer).to.have.been.calledWith(scratch.firstChild);
		expect(inner).to.have.been.calledWith(scratch.firstChild.firstChild);
	});

	it('should pass components to ref functions', () => {
		let ref = spy('ref'),
			instance;
		class Foo extends Component {
			constructor() {
				super();
				instance = this;
			}
			render() {
				return
;
			}
		}
		render(<Foo ref={ref} />, scratch);

		expect(ref).to.have.been.calledOnce.and.calledWith(instance);
	});

	it('should pass rendered DOM from functional components to ref functions', () => {
		let ref = spy('ref');

		const Foo = () =>
;

		let root = render(<Foo ref={ref} />, scratch);
		expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild);

		ref.reset();
		render(<Foo ref={ref} />, scratch, root);
		expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild);

		ref.reset();
		render(, scratch, root);
		expect(ref).to.have.been.calledOnce.and.calledWith(null);
	});

	it('should pass children to ref functions', () => {
		let outer = spy('outer'),
			inner = spy('inner'),
			rerender, inst;
		class Outer extends Component {
			constructor() {
				super();
				rerender = () => this.forceUpdate();
			}
			render() {
				return (

						<Inner ref={outer} />

				);
			}
		}
		class Inner extends Component {
			constructor() {
				super();
				inst = this;
			}
			render() {
				return ;
			}
		}

		let root = render(<Outer />, scratch);

		expect(outer).to.have.been.calledOnce.and.calledWith(inst);
		expect(inner).to.have.been.calledOnce.and.calledWith(inst.base);

		outer.reset();
		inner.reset();

		rerender();

		expect(outer).to.have.been.calledOnce.and.calledWith(inst);
		expect(inner).to.have.been.calledOnce.and.calledWith(inst.base);

		outer.reset();
		inner.reset();

		render(
, scratch, root);

		expect(outer).to.have.been.calledOnce.and.calledWith(null);
		expect(inner).to.have.been.calledOnce.and.calledWith(null);
	});

	it('should pass high-order children to ref functions', () => {
		let outer = spy('outer'),
			inner = spy('inner'),
			innermost = spy('innermost'),
			outerInst,
			innerInst;
		class Outer extends Component {
			constructor() {
				super();
				outerInst = this;
			}
			render() {
				return <Inner ref={inner} />;
			}
		}
		class Inner extends Component {
			constructor() {
				super();
				innerInst = this;
			}
			render() {
				return ;
			}
		}

		let root = render(<Outer ref={outer} />, scratch);

		expect(outer).to.have.been.calledOnce.and.calledWith(outerInst);
		expect(inner).to.have.been.calledOnce.and.calledWith(innerInst);
		expect(innermost).to.have.been.calledOnce.and.calledWith(innerInst.base);

		outer.reset();
		inner.reset();
		innermost.reset();
		root = render(<Outer ref={outer} />, scratch, root);

		expect(outer).to.have.been.calledOnce.and.calledWith(outerInst);
		expect(inner).to.have.been.calledOnce.and.calledWith(innerInst);
		expect(innermost).to.have.been.calledOnce.and.calledWith(innerInst.base);

		outer.reset();
		inner.reset();
		innermost.reset();
		root = render(
, scratch, root);

		expect(outer).to.have.been.calledOnce.and.calledWith(null);
		expect(inner).to.have.been.calledOnce.and.calledWith(null);
		expect(innermost).to.have.been.calledOnce.and.calledWith(null);
	});
});


import { h, render } from '../../src/preact';
/** @jsx h */

describe('keys', () => {
	let scratch;

	before( () => {
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);
	});

	beforeEach( () => {
		scratch.innerHTML = '';
	});

	after( () => {
		scratch.parentNode.removeChild(scratch);
		scratch = null;
	});

	// See developit/preact-compat#21
	it('should remove orphaned keyed nodes', () => {
		let root = render((

1

a

		), scratch);

		root = render((

2

b

		), scratch, root);

		expect(scratch.innerHTML).to.equal('

2

b

');
	});
});


*	@param {VNode} vnode	A VNode with a `nodeName` property that is a reference to a function.

export function buildFunctionalComponent(vnode, context) {

return vnode.nodeName(getNodeProps(vnode), context) || EMPTY_BASE;

return vnode.nodeName(getNodeProps(vnode), context || EMPTY) || EMPTY_BASE;


import VNode from './vnode';
import { optionsHook } from './hooks';
import { falsey } from './util';

const SHARED_TEMP_ARRAY = [];

/** JSX/hyperscript reviver
 *	@see http://jasonformat.com/wtf-is-jsx
 *	@public
 *  @example
 *  /** @jsx h *\/
 *  import { render, h } from 'preact';
 *  render(foo, document.body);
 */
export default function h(nodeName, attributes) {
	let len = arguments.length,
		children, arr, lastSimple;

	if (len>2) {
		children = [];
		for (let i=2; i<len; i++) {
			let p = arguments[i];
			if (falsey(p)) continue;
			if (p.join) {
				arr = p;
			}
			else {
				arr = SHARED_TEMP_ARRAY;
				arr[0] = p;
			}
			for (let j=0; j<arr.length; j++) {
				let child = arr[j],
					simple = !falsey(child) && !(child instanceof VNode);
				if (simple) child = String(child);
				if (simple && lastSimple) {
					children[children.length-1] += child;
				}
				else if (!falsey(child)) {
					children.push(child);
				}
				lastSimple = simple;
			}
		}
	}

	if (attributes && attributes.children) {
		delete attributes.children;
	}

	let p = new VNode(nodeName, attributes || undefined, children || undefined);
	optionsHook('vnode', p);
	return p;
}


root = true

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

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

[*.md]
trim_trailing_whitespace = false


// Update sprint number in package.json and rewrite src/config.json

keep keep keep replace replace keep keep keep

//    grunt.registerTask('test', ['jshint']);

/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, node: true, nomen: true,
indent: 4, maxerr: 50 */
/*global expect, describe, it, beforeEach, afterEach */

"use strict";

var ExtensionsDomain = require("../ExtensionManagerDomain"),
    fs               = require("fs-extra"),
    async            = require("async"),
    path             = require("path");

var testFilesDirectory = path.join(path.dirname(module.filename),
                                    "..",   // node
                                    "..",   // extensibility
                                    "..",   // src
                                    "..",   // brackets
                                    "test",
                                    "spec",
                                    "extension-test-files"),
    installParent      = path.join(path.dirname(module.filename), "extensions"),
    installDirectory   = path.join(installParent, "good"),
    disabledDirectory  = path.join(installParent, "disabled"),
    systemExtensionDirectory = path.join(installParent, "system");

var basicValidExtension       = path.join(testFilesDirectory, "basic-valid-extension.zip"),
    basicValidExtension09     = path.join(testFilesDirectory, "basic-valid-extension-0.9.zip"),
    basicValidExtension2      = path.join(testFilesDirectory, "basic-valid-extension-2.0.zip"),
    missingMain               = path.join(testFilesDirectory, "missing-main.zip"),
    oneLevelDown              = path.join(testFilesDirectory, "one-level-extension-master.zip"),
    incompatibleVersion       = path.join(testFilesDirectory, "incompatible-version.zip"),
    invalidZip                = path.join(testFilesDirectory, "invalid-zip-file.zip"),
    missingPackageJSON        = path.join(testFilesDirectory, "missing-package-json.zip"),
    missingPackageJSONUpdate  = path.join(testFilesDirectory, "missing-package-json-update.zip"),
    missingPackageJSONRenamed = path.join(testFilesDirectory, "added-package-json-test", "missing-package-json.zip");

describe("Package Installation", function () {

    var standardOptions = {
        disabledDirectory: disabledDirectory,
        systemExtensionDirectory: systemExtensionDirectory,
        apiVersion: "0.22.0"
    };

    beforeEach(function (done) {
        fs.mkdirs(installDirectory, function (err) {
            fs.mkdirs(disabledDirectory, function (err) {
                done();
            });
        });
    });

    afterEach(function (done) {
        fs.remove(installParent, function (err) {
            done();
        });
    });

    function checkPaths(pathsToCheck, callback) {
        var existsCalls = [];
        pathsToCheck.forEach(function (path) {
            existsCalls.push(function (callback) {
                fs.exists(path, async.apply(callback, null));
            });
        });

        async.parallel(existsCalls, function (err, results) {
            expect(err).toBeNull();
            results.forEach(function (result, num) {
                expect(result ? "" : pathsToCheck[num] + " does not exist").toEqual("");
            });
            callback();
        });
    }

    it("should validate the package", function (done) {
        ExtensionsDomain._cmdInstall(missingMain, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(1);
            expect(result.installationStatus).toEqual("FAILED");
            done();
        });
    });

    it("should work fine if all is well", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) {
            var extensionDirectory = path.join(installDirectory, "basic-valid-extension");

            expect(err).toBeNull();
            var errors = result.errors;
            expect(errors.length).toEqual(0);
            expect(result.metadata.name).toEqual("basic-valid-extension");
            expect(result.name).toEqual("basic-valid-extension");
            expect(result.installedTo).toEqual(extensionDirectory);
            expect(result.installationStatus).toEqual("INSTALLED");

            var pathsToCheck = [
                path.join(extensionDirectory, "package.json"),
                path.join(extensionDirectory, "main.js")
            ];

            checkPaths(pathsToCheck, done);
        });
    });

    it("should signal if an update installation is required", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) {
            var extensionDirectory = path.join(installDirectory, "basic-valid-extension");

            expect(err).toBeNull();
            expect(result.installedTo).toEqual(extensionDirectory);
            expect(result.installationStatus).toEqual("INSTALLED");
            ExtensionsDomain._cmdInstall(basicValidExtension2, installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                expect(result.installationStatus).toEqual("NEEDS_UPDATE");
                expect(result.localPath).toEqual(basicValidExtension2);
                done();
            });
        });
    });

    it("should successfully update an extension", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) {
            var extensionDirectory = path.join(installDirectory, "basic-valid-extension");

            expect(err).toBeNull();
            ExtensionsDomain._cmdInstall(basicValidExtension2, installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                ExtensionsDomain._cmdUpdate(basicValidExtension2, installDirectory, standardOptions, function (err, result) {
                    expect(err).toBeNull();
                    expect(result.installationStatus).toBe("INSTALLED");
                    var packageInfo = fs.readJsonSync(path.join(result.installedTo, "package.json"));
                    expect(packageInfo.version).toBe("2.0.0");
                    done();
                });
            });
        });
    });

    it("should signal an update if a package.json appears", function (done) {
        ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            expect(result.installationStatus).toEqual("INSTALLED");
            ExtensionsDomain._cmdInstall(missingPackageJSONUpdate, installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                expect(result.installationStatus).toEqual("NEEDS_UPDATE");
                done();
            });
        });
    });

    // This is mildly redundant. the validation check should catch this.
    // But, I wanted to be sure that the install function doesn't try to
    // do anything with the file before validation.
    it("should fail for missing package", function (done) {
        ExtensionsDomain._cmdInstall(path.join(testFilesDirectory, "NOT A PACKAGE"),
                                     installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                var errors = result.errors;
                expect(errors.length).toEqual(1);
                expect(errors[0][0]).toEqual("NOT_FOUND_ERR");
                done();
            });
    });

    it("should not install by default if the same version is already installed", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                expect(result.installationStatus).toEqual("SAME_VERSION");
                done();
            });
        });
    });

    it("should not install by default if an older version is already installed", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            ExtensionsDomain._cmdInstall(basicValidExtension09, installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                expect(result.installationStatus).toEqual("OLDER_VERSION");
                done();
            });
        });
    });

    it("should not install by default if the same legacy extension is already installed", function (done) {
        ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                expect(result.installationStatus).toEqual("ALREADY_INSTALLED");
                done();
            });
        });
    });

    it("should yield an error if there's no disabled directory set", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, { apiVersion: "0.22.0" }, function (err, result) {
            expect(err.message).toEqual("MISSING_REQUIRED_OPTIONS");
            done();
        });
    });

    it("should yield an error if there's no apiVersion set", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, { disabledDirectory: disabledDirectory }, function (err, result) {
            expect(err.message).toEqual("MISSING_REQUIRED_OPTIONS");
            done();
        });
    });

    it("should derive the name from the zip if there's no package.json", function (done) {
        ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            expect(result.disabledReason).toBeNull();
            var extensionDirectory = path.join(installDirectory, "missing-package-json");
            var pathsToCheck = [
                path.join(extensionDirectory, "main.js")
            ];
            checkPaths(pathsToCheck, done);
        });
    });

    it("should install with the common prefix removed", function (done) {
        ExtensionsDomain._cmdInstall(oneLevelDown, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            var extensionDirectory = path.join(installDirectory, "one-level-extension");
            var pathsToCheck = [
                path.join(extensionDirectory, "main.js"),
                path.join(extensionDirectory, "package.json"),
                path.join(extensionDirectory, "lib", "foo.js")
            ];
            checkPaths(pathsToCheck, done);
        });
    });

    it("should disable extensions that are not compatible with the current Brackets API", function (done) {
        ExtensionsDomain._cmdInstall(incompatibleVersion, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            expect(result.installationStatus).toEqual("DISABLED");
            expect(result.disabledReason).toEqual("API_NOT_COMPATIBLE");
            var extensionDirectory = path.join(disabledDirectory, "incompatible-version");
            var pathsToCheck = [
                path.join(extensionDirectory, "main.js"),
                path.join(extensionDirectory, "package.json")
            ];
            checkPaths(pathsToCheck, done);
        });
    });

    it("should not have trouble with invalid zip files", function (done) {
        ExtensionsDomain._cmdInstall(invalidZip, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            expect(result.errors.length).toEqual(1);
            done();
        });
    });

    it("should remove an installed package", function (done) {
        ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            expect(fs.existsSync(result.installedTo)).toBe(true);
            ExtensionsDomain._cmdRemove(result.installedTo, function (err) {
                expect(err).toBeNull();
                expect(fs.existsSync(result.installedTo)).toBe(false);
                done();
            });
        });
    });

    it("should handle a package renamed with package.json", function (done) {
        ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) {
            expect(err).toBeNull();
            expect(fs.existsSync(result.installedTo)).toBe(true);
            var legacyDirectory = result.installedTo;
            ExtensionsDomain._cmdInstall(missingPackageJSONRenamed, installDirectory, standardOptions, function (err, result) {
                expect(err).toBeNull();
                expect(result.installationStatus).toBe("NEEDS_UPDATE");
                expect(result.name).toBe("missing-package-json");
                ExtensionsDomain._cmdUpdate(missingPackageJSONRenamed, installDirectory, standardOptions, function (err, result) {
                    expect(err).toBeNull();
                    expect(result.installationStatus).toBe("INSTALLED");
                    expect(result.name).toBe("renamed-in-package-json");
                    expect(fs.existsSync(legacyDirectory)).toBe(false);
                    done();
                });
            });
        });
    });
});


/build/*
node_modules
*.node
*.sh
*.swp
.lock*
npm-debug.log
.idea


{{DIALOG_TITLE}}

        {{DIALOG_TEXT}}

        {{DIALOG_OK}}


//
// Accordion
// --------------------------------------------------

// Parent container
.accordion {
  margin-bottom: @baseLineHeight;
}

// Group == heading + body
.accordion-group {
  margin-bottom: 2px;
  border: 1px solid #e5e5e5;
  .border-radius(@baseBorderRadius);
}
.accordion-heading {
  border-bottom: 0;
}
.accordion-heading .accordion-toggle {
  display: block;
  padding: 8px 15px;
}

// General toggle styles
.accordion-toggle {
  cursor: pointer;
}

// Inner needs the styles because you can't animate properly with any styles on the element
.accordion-inner {
  padding: 9px 15px;
  border-top: 1px solid #e5e5e5;
}


ExtensionsDomain._cmdInstall(basicValidExtension2, installDirectory, standardOptions, function (err, result) {

ExtensionsDomain._cmdUpdate(basicValidExtension2, installDirectory, standardOptions, function (err, result) {

/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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.
 *
 */
/*global module, require*/
module.exports = function (grunt) {
    'use strict';

    var common = require("./tasks/lib/common")(grunt);

    // Project configuration.
    grunt.initConfig({
        pkg  : grunt.file.readJSON("package.json"),
        meta : {
            src   : [
                'src/**/*.js',
                '!src/thirdparty/**',
                '!src/widgets/bootstrap-*.js',
                '!src/extensions/**/unittest-files/**/*.js',
                '!src/extensions/**/thirdparty/**/*.js',
                '!src/extensions/dev/**',
                '!src/extensions/disabled/**',
                '!**/node_modules/**/*.js',
                '!src/**/*-min.js',
                '!src/**/*.min.js'
            ],
            test : [
                'test/**/*.js',
                '!test/perf/*-files/**/*.js',
                '!test/spec/*-files/**/*.js',
                '!test/smokes/**',
                '!test/temp/**',
                '!test/thirdparty/**',
                '!test/**/node_modules/**/*.js'
            ],
            grunt: [
                'Gruntfile.js',
                'tasks/**/*.js'
            ],
            /* specs that can run in phantom.js */
            specs : [
                'test/spec/CommandManager-test.js',
                //'test/spec/LanguageManager-test.js',
                //'test/spec/PreferencesManager-test.js',
                'test/spec/ViewUtils-test.js'
            ]
        },
        watch: {
            all : {
                files: ['**/*', '!**/node_modules/**'],
                tasks: ['jshint']
            },
            grunt : {
                files: ['<%= meta.grunt %>', 'tasks/**/*'],
                tasks: ['jshint:grunt']
            },
            src : {
                files: ['<%= meta.src %>', 'src/**/*'],
                tasks: ['jshint:src']
            },
            test : {
                files: ['<%= meta.test %>', 'test/**/*'],
                tasks: ['jshint:test']
            }
        },
        /* FIXME (jasonsanjose): how to handle extension tests */
        jasmine : {
            src : 'undefined.js', /* trick the default runner to run without importing src files */
            options : {
                junit : {
                    path: 'test/results',
                    consolidate: true
                },
                specs : '<%= meta.specs %>',
                /* Keep in sync with test/SpecRunner.html dependencies */
                vendor : [
                    'src/thirdparty/jquery-2.0.1.min.js',
                    'src/thirdparty/CodeMirror2/lib/codemirror.js',
                    'src/thirdparty/CodeMirror2/lib/util/dialog.js',
                    'src/thirdparty/CodeMirror2/lib/util/searchcursor.js',
                    'src/thirdparty/CodeMirror2/addon/edit/closetag.js',
                    'src/thirdparty/CodeMirror2/addon/selection/active-line.js',
                    'src/thirdparty/mustache/mustache.js',
                    'src/thirdparty/path-utils/path-utils.min',
                    'src/thirdparty/less-1.3.3.min.js'
                ],
                helpers : [
                    'test/spec/PhantomHelper.js'
                ],
                template : require('grunt-template-jasmine-requirejs'),
                templateOptions: {
                    requireConfig : {
                        baseUrl: 'src',
                        paths: {
                            'test' : '../test',
                            'perf' : '../test/perf',
                            'spec' : '../test/spec',
                            'text' : 'thirdparty/text/text',
                            'i18n' : 'thirdparty/i18n/i18n'
                        }
                    }
                }
            }
        },
        'jasmine-node': {
            run: {
                spec: 'src/extensibility/node/spec/'
            }
        },
        jshint: {
            all: [
                '<%= meta.grunt %>',
                '<%= meta.src %>',
                '<%= meta.test %>'
            ],
            grunt:  '<%= meta.grunt %>',
            src:    '<%= meta.src %>',
            test:   '<%= meta.test %>',
            /* use strict options to mimic JSLINT until we migrate to JSHINT in Brackets */
            options: {
                jshintrc: '.jshintrc'
            }
        },
        shell: {
            repo: grunt.option("shell-repo") || "../brackets-shell",
            mac: "<%= shell.repo %>/installer/mac/staging/<%= pkg.name %>.app",
            win: "<%= shell.repo %>/installer/win/staging/<%= pkg.name %>.exe"
        }
    });

    // load dependencies
    grunt.loadTasks('tasks');
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-jasmine-node');

    // task: install
    grunt.registerTask('install', ['write-config']);

    // task: test
    //grunt.registerTask('test', ['jshint', 'jasmine']);
    //grunt.registerTask('test', ['jshint', 'jasmine-node']);
    grunt.registerTask('test', ['jshint']);

    // task: set-sprint
    // Update sprint number in package.json and rewrite src/config.json
    grunt.registerTask('set-sprint', ['update-sprint-number', 'write-config']);

    // Default task.
    grunt.registerTask('default', ['test']);
};


test:
	@./node_modules/.bin/mocha \
		--reporter spec \
		--require should

.PHONY: test


//
// Navbars (Redux)
// --------------------------------------------------

// COMMON STYLES
// -------------

// Base class and wrapper
.navbar {
  overflow: visible;
  margin-bottom: @baseLineHeight;

  // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar
  *position: relative;
  *z-index: 2;
}

// Inner for background effects
// Gradient is applied to its own element because overflow visible is not honored by IE when filter is present
.navbar-inner {
  min-height: @navbarHeight;
  padding-left:  20px;
  padding-right: 20px;
  #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground);
  border: 1px solid @navbarBorder;
  .border-radius(@baseBorderRadius);
  .box-shadow(0 1px 4px rgba(0,0,0,.065));

  // Prevent floats from breaking the navbar
  .clearfix();
}

// Set width to auto for default container
// We then reset it for fixed navbars in the #gridSystem mixin
.navbar .container {
  width: auto;
}

// Override the default collapsed state
.nav-collapse.collapse {
  height: auto;
  overflow: visible;
}

// Brand: website or project name
// -------------------------
.navbar .brand {
  float: left;
  display: block;
  // Vertically center the text given @navbarHeight
  padding: ((@navbarHeight - @baseLineHeight) / 2) 20px ((@navbarHeight - @baseLineHeight) / 2);
  margin-left: -20px; // negative indent to left-align the text down the page
  font-size: 20px;
  font-weight: 200;
  color: @navbarBrandColor;
  text-shadow: 0 1px 0 @navbarBackgroundHighlight;
  &:hover,
  &:focus {
    text-decoration: none;
  }
}

// Plain text in topbar
// -------------------------
.navbar-text {
  margin-bottom: 0;
  line-height: @navbarHeight;
  color: @navbarText;
}

// Janky solution for now to account for links outside the .nav
// -------------------------
.navbar-link {
  color: @navbarLinkColor;
  &:hover,
  &:focus {
    color: @navbarLinkColorHover;
  }
}

// Dividers in navbar
// -------------------------
.navbar .divider-vertical {
  height: @navbarHeight;
  margin: 0 9px;
  border-left: 1px solid @navbarBackground;
  border-right: 1px solid @navbarBackgroundHighlight;
}

// Buttons in navbar
// -------------------------
.navbar .btn,
.navbar .btn-group {
  .navbarVerticalAlign(30px); // Vertically center in navbar
}
.navbar .btn-group .btn,
.navbar .input-prepend .btn,
.navbar .input-append .btn,
.navbar .input-prepend .btn-group,
.navbar .input-append .btn-group {
  margin-top: 0; // then undo the margin here so we don't accidentally double it
}

// Navbar forms
// -------------------------
.navbar-form {
  margin-bottom: 0; // remove default bottom margin
  .clearfix();
  input,
  select,
  .radio,
  .checkbox {
    .navbarVerticalAlign(30px); // Vertically center in navbar
  }
  input,
  select,
  .btn {
    display: inline-block;
    margin-bottom: 0;
  }
  input[type="image"],
  input[type="checkbox"],
  input[type="radio"] {
    margin-top: 3px;
  }
  .input-append,
  .input-prepend {
    margin-top: 5px;
    white-space: nowrap; // preven two  items from separating within a .navbar-form that has .pull-left
    input {
      margin-top: 0; // remove the margin on top since it's on the parent
    }
  }
}

// Navbar search
// -------------------------
.navbar-search {
  position: relative;
  float: left;
  .navbarVerticalAlign(30px); // Vertically center in navbar
  margin-bottom: 0;
  .search-query {
    margin-bottom: 0;
    padding: 4px 14px;
    #font > .sans-serif(13px, normal, 1);
    .border-radius(15px); // redeclare because of specificity of the type attribute
  }
}

// Static navbar
// -------------------------

.navbar-static-top {
  position: static;
  margin-bottom: 0; // remove 18px margin for default navbar
  .navbar-inner {
    .border-radius(0);
  }
}

// Fixed navbar
// -------------------------

// Shared (top/bottom) styles
.navbar-fixed-top,
.navbar-fixed-bottom {
  position: fixed;
  right: 0;
  left: 0;
  z-index: @zindexFixedNavbar;
  margin-bottom: 0; // remove 18px margin for default navbar
}
.navbar-fixed-top .navbar-inner,
.navbar-static-top .navbar-inner {
  border-width: 0 0 1px;
}
.navbar-fixed-bottom .navbar-inner {
  border-width: 1px 0 0;
}
.navbar-fixed-top .navbar-inner,
.navbar-fixed-bottom .navbar-inner {
  padding-left:  0;
  padding-right: 0;
  .border-radius(0);
}

// Reset container width
// Required here as we reset the width earlier on and the grid mixins don't override early enough
.navbar-static-top .container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
  #grid > .core > .span(@gridColumns);
}

// Fixed to top
.navbar-fixed-top {
  top: 0;
}
.navbar-fixed-top,
.navbar-static-top {
  .navbar-inner {
    .box-shadow(~"0 1px 10px rgba(0,0,0,.1)");
  }
}

// Fixed to bottom
.navbar-fixed-bottom {
  bottom: 0;
  .navbar-inner {
    .box-shadow(~"0 -1px 10px rgba(0,0,0,.1)");
  }
}

// NAVIGATION
// ----------

.navbar .nav {
  position: relative;
  left: 0;
  display: block;
  float: left;
  margin: 0 10px 0 0;
}
.navbar .nav.pull-right {
  float: right; // redeclare due to specificity
  margin-right: 0; // remove margin on float right nav
}
.navbar .nav > li {
  float: left;
}

// Links
.navbar .nav > li > a {
  float: none;
  // Vertically center the text given @navbarHeight
  padding: ((@navbarHeight - @baseLineHeight) / 2) 15px ((@navbarHeight - @baseLineHeight) / 2);
  color: @navbarLinkColor;
  text-decoration: none;
  text-shadow: 0 1px 0 @navbarBackgroundHighlight;
}
.navbar .nav .dropdown-toggle .caret {
  margin-top: 8px;
}

// Hover/focus
.navbar .nav > li > a:focus,
.navbar .nav > li > a:hover {
  background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover/:focus from .active
  color: @navbarLinkColorHover;
  text-decoration: none;
}

// Active nav items
.navbar .nav > .active > a,
.navbar .nav > .active > a:hover,
.navbar .nav > .active > a:focus {
  color: @navbarLinkColorActive;
  text-decoration: none;
  background-color: @navbarLinkBackgroundActive;
  .box-shadow(inset 0 3px 8px rgba(0,0,0,.125));
}

// Navbar button for toggling navbar items in responsive layouts
// These definitions need to come after '.navbar .btn'
.navbar .btn-navbar {
  display: none;
  float: right;
  padding: 7px 10px;
  margin-left: 5px;
  margin-right: 5px;
  .buttonBackground(darken(@navbarBackgroundHighlight, 5%), darken(@navbarBackground, 5%));
  .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)");
}
.navbar .btn-navbar .icon-bar {
  display: block;
  width: 18px;
  height: 2px;
  background-color: #f5f5f5;
  .border-radius(1px);
  .box-shadow(0 1px 0 rgba(0,0,0,.25));
}
.btn-navbar .icon-bar + .icon-bar {
  margin-top: 3px;
}

// Dropdown menus
// --------------

// Menu position and menu carets
.navbar .nav > li > .dropdown-menu {
  &:before {
    content: '';
    display: inline-block;
    border-left:   7px solid transparent;
    border-right:  7px solid transparent;
    border-bottom: 7px solid #ccc;
    border-bottom-color: @dropdownBorder;
    position: absolute;
    top: -7px;
    left: 9px;
  }
  &:after {
    content: '';
    display: inline-block;
    border-left:   6px solid transparent;
    border-right:  6px solid transparent;
    border-bottom: 6px solid @dropdownBackground;
    position: absolute;
    top: -6px;
    left: 10px;
  }
}
// Menu position and menu caret support for dropups via extra dropup class
.navbar-fixed-bottom .nav > li > .dropdown-menu {
  &:before {
    border-top: 7px solid #ccc;
    border-top-color: @dropdownBorder;
    border-bottom: 0;
    bottom: -7px;
    top: auto;
  }
  &:after {
    border-top: 6px solid @dropdownBackground;
    border-bottom: 0;
    bottom: -6px;
    top: auto;
  }
}

// Caret should match text color on hover/focus
.navbar .nav li.dropdown > a:hover .caret,
.navbar .nav li.dropdown > a:focus .caret {
  border-top-color: @navbarLinkColorHover;
  border-bottom-color: @navbarLinkColorHover;
}

// Remove background color from open dropdown
.navbar .nav li.dropdown.open > .dropdown-toggle,
.navbar .nav li.dropdown.active > .dropdown-toggle,
.navbar .nav li.dropdown.open.active > .dropdown-toggle {
  background-color: @navbarLinkBackgroundActive;
  color: @navbarLinkColorActive;
}
.navbar .nav li.dropdown > .dropdown-toggle .caret {
  border-top-color: @navbarLinkColor;
  border-bottom-color: @navbarLinkColor;
}
.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
  border-top-color: @navbarLinkColorActive;
  border-bottom-color: @navbarLinkColorActive;
}

// Right aligned menus need alt position
.navbar .pull-right > li > .dropdown-menu,
.navbar .nav > li > .dropdown-menu.pull-right {
  left: auto;
  right: 0;
  &:before {
    left: auto;
    right: 12px;
  }
  &:after {
    left: auto;
    right: 13px;
  }
  .dropdown-menu {
    left: auto;
    right: 100%;
    margin-left: 0;
    margin-right: -1px;
    .border-radius(6px 0 6px 6px);
  }
}

// Inverted navbar
// -------------------------

.navbar-inverse {

  .navbar-inner {
    #gradient > .vertical(@navbarInverseBackgroundHighlight, @navbarInverseBackground);
    border-color: @navbarInverseBorder;
  }

  .brand,
  .nav > li > a {
    color: @navbarInverseLinkColor;
    text-shadow: 0 -1px 0 rgba(0,0,0,.25);
    &:hover,
    &:focus {
      color: @navbarInverseLinkColorHover;
    }
  }

  .brand {
    color: @navbarInverseBrandColor;
  }

  .navbar-text {
    color: @navbarInverseText;
  }

  .nav > li > a:focus,
  .nav > li > a:hover {
    background-color: @navbarInverseLinkBackgroundHover;
    color: @navbarInverseLinkColorHover;
  }

  .nav .active > a,
  .nav .active > a:hover,
  .nav .active > a:focus {
    color: @navbarInverseLinkColorActive;
    background-color: @navbarInverseLinkBackgroundActive;
  }

  // Inline text links
  .navbar-link {
    color: @navbarInverseLinkColor;
    &:hover,
    &:focus {
      color: @navbarInverseLinkColorHover;
    }
  }

  // Dividers in navbar
  .divider-vertical {
    border-left-color: @navbarInverseBackground;
    border-right-color: @navbarInverseBackgroundHighlight;
  }

  // Dropdowns
  .nav li.dropdown.open > .dropdown-toggle,
  .nav li.dropdown.active > .dropdown-toggle,
  .nav li.dropdown.open.active > .dropdown-toggle {
    background-color: @navbarInverseLinkBackgroundActive;
    color: @navbarInverseLinkColorActive;
  }
  .nav li.dropdown > a:hover .caret,
  .nav li.dropdown > a:focus .caret {
    border-top-color: @navbarInverseLinkColorActive;
    border-bottom-color: @navbarInverseLinkColorActive;
  }
  .nav li.dropdown > .dropdown-toggle .caret {
    border-top-color: @navbarInverseLinkColor;
    border-bottom-color: @navbarInverseLinkColor;
  }
  .nav li.dropdown.open > .dropdown-toggle .caret,
  .nav li.dropdown.active > .dropdown-toggle .caret,
  .nav li.dropdown.open.active > .dropdown-toggle .caret {
    border-top-color: @navbarInverseLinkColorActive;
    border-bottom-color: @navbarInverseLinkColorActive;
  }

  // Navbar search
  .navbar-search {
    .search-query {
      color: @white;
      background-color: @navbarInverseSearchBackground;
      border-color: @navbarInverseSearchBorder;
      .box-shadow(~"inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15)");
      .transition(none);
      .placeholder(@navbarInverseSearchPlaceholderColor);

      // Focus states (we use .focused since IE7-8 and down doesn't support :focus)
      &:focus,
      &.focused {
        padding: 5px 15px;
        color: @grayDark;
        text-shadow: 0 1px 0 @white;
        background-color: @navbarInverseSearchBackgroundFocus;
        border: 0;
        .box-shadow(0 0 3px rgba(0,0,0,.15));
        outline: 0;
      }
    }
  }

  // Navbar collapse button
  .btn-navbar {
    .buttonBackground(darken(@navbarInverseBackgroundHighlight, 5%), darken(@navbarInverseBackground, 5%));
  }

}


/*!
 * Connect - urlencoded
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var utils = require('../utils')
  , _limit = require('./limit')
  , qs = require('qs');

/**
 * noop middleware.
 */

function noop(req, res, next) {
  next();
}

/**
 * Urlencoded:
 *
 *  Parse x-ww-form-urlencoded request bodies,
 *  providing the parsed object as `req.body`.
 *
 * Options:
 *
 *    - `limit`  byte limit disabled by default
 *
 * @param {Object} options
 * @return {Function}
 * @api public
 */

exports = module.exports = function(options){
  options = options || {};

  var limit = options.limit
    ? _limit(options.limit)
    : noop;

  return function urlencoded(req, res, next) {
    if (req._body) return next();
    req.body = req.body || {};

    if (!utils.hasBody(req)) return next();

    // check Content-Type
    if ('application/x-www-form-urlencoded' != utils.mime(req)) return next();

    // flag as parsed
    req._body = true;

    // parse
    limit(req, res, function(err){
      if (err) return next(err);
      var buf = '';
      req.setEncoding('utf8');
      req.on('data', function(chunk){ buf += chunk });
      req.on('end', function(){
        try {
          req.body = buf.length
            ? qs.parse(buf, options)
            : {};
          next();
        } catch (err){
          err.body = buf;
          next(err);
        }
      });
    });
  }
};


var packageInfo = fs.readJsonSync(path.join(result.installedTo, "package.json"));


(function( $ ) {

var disabled = TestHelpers.tabs.disabled,
	equalHeight = TestHelpers.tabs.equalHeight,
	state = TestHelpers.tabs.state;

module( "tabs: options" );

test( "{ active: default }", function() {
	expect( 4 );

	var element = $( "#tabs1" ).tabs();
	equal( element.tabs( "option", "active" ), 0, "should be 0 by default" );
	state( element, 1, 0, 0 );
	element.tabs( "destroy" );

	location.hash = "#fragment-3";
	element = $( "#tabs1" ).tabs();
	equal( element.tabs( "option", "active" ), 2, "should be 2 based on URL" );
	state( element, 0, 0, 1 );
	element.tabs( "destroy" );
	location.hash = "#";
});

test( "{ active: false }", function() {
	expect( 7 );

	var element = $( "#tabs1" ).tabs({
		active: false,
		collapsible: true
	});
	state( element, 0, 0, 0 );
	equal( element.find( ".ui-tabs-nav .ui-state-active" ).length, 0, "no tabs selected" );
	strictEqual( element.tabs( "option", "active" ), false );

	element.tabs( "option", "collapsible", false );
	state( element, 1, 0, 0 );
	equal( element.tabs( "option", "active" ), 0 );

	element.tabs( "destroy" );
	element.tabs({
		active: false
	});
	state( element, 1, 0, 0 );
	strictEqual( element.tabs( "option", "active" ), 0 );
});

test( "{ active: Number }", function() {
	expect( 8 );

	var element = $( "#tabs1" ).tabs({
		active: 2
	});
	equal( element.tabs( "option", "active" ), 2 );
	state( element, 0, 0, 1 );

	element.tabs( "option", "active", 0 );
	equal( element.tabs( "option", "active" ), 0 );
	state( element, 1, 0, 0 );

	element.find( ".ui-tabs-nav .ui-tabs-anchor" ).eq( 1 ).click();
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );

	element.tabs( "option", "active", 10 );
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );
});

if ( $.uiBackCompat === false ) {
	test( "{ active: -Number }", function() {
		var element = $( "#tabs1" ).tabs({
			active: -1
		});
		equal( element.tabs( "option", "active" ), 2 );
		state( element, 0, 0, 1 );

		element.tabs( "option", "active", -2 );
		equal( element.tabs( "option", "active" ), 1 );
		state( element, 0, 1, 0 );

		element.tabs( "option", "active", -10 );
		equal( element.tabs( "option", "active" ), 1 );
		state( element, 0, 1, 0 );

		element.tabs( "option", "active", -3 );
		equal( element.tabs( "option", "active" ), 0 );
		state( element, 1, 0, 0 );
	});
}

test( "active - mismatched tab/panel order", function() {
	expect( 3 );

	location.hash = "#tabs7-2";
	var element = $( "#tabs7" ).tabs();
	equal( element.tabs( "option", "active" ), 1, "should be 1 based on URL" );
	state( element, 0, 1 );
	element.tabs( "option", "active", 0 );
	state( element, 1, 0 );
	location.hash = "#";
});

test( "{ collapsible: false }", function() {
	expect( 4 );

	var element = $( "#tabs1" ).tabs({
		active: 1
	});
	element.tabs( "option", "active", false );
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );

	element.find( ".ui-state-active .ui-tabs-anchor" ).eq( 1 ).click();
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );
});

test( "{ collapsible: true }", function() {
	expect( 6 );

	var element = $( "#tabs1" ).tabs({
		active: 1,
		collapsible: true
	});

	element.tabs( "option", "active", false );
	equal( element.tabs( "option", "active" ), false );
	state( element, 0, 0, 0 );

	element.tabs( "option", "active", 1 );
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );

	element.find( ".ui-state-active .ui-tabs-anchor" ).click();
	equal( element.tabs( "option", "active" ), false );
	state( element, 0, 0, 0 );
});

test( "disabled", function() {
	expect( 10 );

	// fully enabled by default
	var element = $( "#tabs1" ).tabs();
	disabled( element, false );

	// disable single tab
	element.tabs( "option", "disabled", [ 1 ] );
	disabled( element, [ 1 ] );

	// disabled active tab
	element.tabs( "option", "disabled", [ 0, 1 ] );
	disabled( element, [ 0, 1 ] );

	// disable all tabs
	element.tabs( "option", "disabled", [ 0, 1, 2 ] );
	disabled( element, true );

	// enable all tabs
	element.tabs( "option", "disabled", [] );
	disabled( element, false );
});

test( "{ event: null }", function() {
	expect( 5 );

	var element = $( "#tabs1" ).tabs({
		event: null
	});
	state( element, 1, 0, 0 );

	element.tabs( "option", "active", 1 );
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );

	// ensure default click handler isn't bound
	element.find( ".ui-tabs-nav .ui-tabs-anchor" ).eq( 2 ).click();
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );
});

test( "{ event: custom }", function() {
	expect( 11 );

	var element = $( "#tabs1" ).tabs({
		event: "custom1 custom2"
	});
	state( element, 1, 0, 0 );

	element.find( ".ui-tabs-nav .ui-tabs-anchor" ).eq( 1 ).trigger( "custom1" );
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );

	// ensure default click handler isn't bound
	element.find( ".ui-tabs-nav .ui-tabs-anchor" ).eq( 2 ).trigger( "click" );
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );

	element.find( ".ui-tabs-nav .ui-tabs-anchor" ).eq( 2 ).trigger( "custom2" );
	equal( element.tabs( "option", "active" ), 2 );
	state( element, 0, 0, 1 );

	element.tabs( "option", "event", "custom3" );

	// ensure old event handlers are unbound
	element.find( ".ui-tabs-nav .ui-tabs-anchor" ).eq( 1 ).trigger( "custom1" );
	equal( element.tabs( "option", "active" ), 2 );
	state( element, 0, 0, 1 );

	element.find( ".ui-tabs-nav .ui-tabs-anchor" ).eq( 1 ).trigger( "custom3" );
	equal( element.tabs( "option", "active" ), 1 );
	state( element, 0, 1, 0 );
});

test( "{ heightStyle: 'auto' }", function() {
	expect( 2 );
	var element = $( "#tabs8" ).tabs({ heightStyle: "auto" });
	equalHeight( element, 45 );
});

test( "{ heightStyle: 'content' }", function() {
	expect( 2 );
	var element = $( "#tabs8" ).tabs({ heightStyle: "content" }),
		sizes = element.find( ".ui-tabs-panel" ).map(function() {
			return $( this ).height();
		}).get();
	equal( sizes[ 0 ], 45 );
	equal( sizes[ 1 ], 15 );
});

test( "{ heightStyle: 'fill' }", function() {
	expect( 2 );
	$( "#tabs8Wrapper" ).height( 500 );
	var element = $( "#tabs8" ).tabs({ heightStyle: "fill" });
	equalHeight( element, 485 );
});

test( "{ heightStyle: 'fill' } with sibling", function() {
	expect( 2 );
	$( "#tabs8Wrapper" ).height( 500 );
	$( "
Lorem Ipsum
" )
		.css({
			height: 50,
			marginTop: 20,
			marginBottom: 30
		})
		.prependTo( "#tabs8Wrapper" );
	var element = $( "#tabs8" ).tabs({ heightStyle: "fill" });
	equalHeight( element, 385 );
});

test( "{ heightStyle: 'fill' } with multiple siblings", function() {
	expect( 2 );
	$( "#tabs8Wrapper" ).height( 500 );
	$( "
Lorem Ipsum
" )
		.css({
			height: 50,
			marginTop: 20,
			marginBottom: 30
		})
		.prependTo( "#tabs8Wrapper" );
	$( "
Lorem Ipsum
" )
		.css({
			height: 50,
			marginTop: 20,
			marginBottom: 30,
			position: "absolute"
		})
		.prependTo( "#tabs8Wrapper" );
	$( "
Lorem Ipsum
" )
		.css({
			height: 25,
			marginTop: 10,
			marginBottom: 15
		})
		.prependTo( "#tabs8Wrapper" );
	var element = $( "#tabs8" ).tabs({ heightStyle: "fill" });
	equalHeight( element, 335 );
});

// TODO: add animation tests

}( jQuery ) );