<commit_before>define(["Test", "Tone/core/Context", "Tone/core/Tone", "helper/Offline"],
	function (Test, Context, Tone, Offline) {

	describe("Context", function(){

		context("AudioContext", function(){

			it ("extends the AudioContext methods", function(){
				var ctx = new Context();
				expect(ctx).to.have.property("createGain");
				expect(ctx.createGain()).to.be.instanceOf(GainNode);
				expect(ctx).to.have.property("createOscillator");
				expect(ctx.createOscillator()).to.be.instanceOf(OscillatorNode);
				expect(ctx).to.have.property("createDelay");
				expect(ctx.createDelay()).to.be.instanceOf(DelayNode);
				return ctx.dispose();
			});

			it ("clock is running", function(done){
				var interval = setInterval(function(){
					if (Tone.context.currentTime > 0.5){
						clearInterval(interval);
						done();
					}
				}, 20);
			});

			it ("can be created an disposed", function(){
				var ctx = new Context();
				return ctx.dispose().then(function(){
					Test.wasDisposed(ctx);
				});
			});

			it ("can be constructed with an options object", function(){
				var ctx = new Context({
					"lookAhead" : 0.2,
					"latencyHint" : "fastest",
					"clockSource" : "timeout"
				});
				expect(ctx.lookAhead).to.equal(0.2);
				expect(ctx.latencyHint).to.equal("fastest");
				expect(ctx.clockSource).to.equal("timeout");
				return ctx.dispose();
			});
		});

		context("clockSource", function(){

			var ctx;
			beforeEach(function(){
				ctx = new Context();
				return ctx.ready();
			});

			afterEach(function(){
				return ctx.dispose();
			});

			it ("defaults to 'worker'", function(){
				expect(ctx.clockSource).to.equal("worker");
			});

			it ("provides callback", function(done){
				expect(ctx.clockSource).to.equal("worker");
				ctx.setTimeout(function(){
					done();
				}, 0.1);
			});

			it ("can be set to 'timeout'", function(done){
				ctx.clockSource = "timeout";
				expect(ctx.clockSource).to.equal("timeout");
				ctx.setTimeout(function(){
					done();
				}, 0.1);
			});

			it ("can be set to 'offline'", function(done){
				ctx.clockSource = "offline";
				expect(ctx.clockSource).to.equal("offline");
				//provides no callback
				ctx.setTimeout(function(){
					throw new Error("shouldn't be called");
				}, 0.1);
				setTimeout(function(){
					done();
				}, 200);
			});
		});

		context("setTimeout", function(){

			var ctx;
			beforeEach(function(){
				ctx = new Context();
				return ctx.ready();
			});

			afterEach(function(){
				return ctx.dispose();
			});

			it ("can set a timeout", function(done){
				ctx.setTimeout(function(){
					done();
				}, 0.1);
			});

			it ("returns an id", function(){
				expect(ctx.setTimeout(function(){}, 0.1)).to.be.a("number");
				//try clearing a random ID, shouldn't cause any errors
				ctx.clearTimeout(-2);
			});

			it ("timeout is not invoked when cancelled", function(done){
				var id = ctx.setTimeout(function(){
					throw new Error("shouldn't be invoked");
				}, 0.01);
				ctx.clearTimeout(id);
				ctx.setTimeout(function(){
					done();
				}, 0.02);
			});

			it ("order is maintained", function(done){
				var wasInvoked = false;
				ctx.setTimeout(function(){
					expect(wasInvoked).to.be.true;
					done();
				}, 0.011);
				ctx.setTimeout(function(){
					wasInvoked = true;
				}, 0.01);
			});

			it ("is invoked in the offline context", function(){
				return Offline(function(Transport){
					Transport.context.setTimeout(function(){
						expect(Tone.now()).to.be.closeTo(0.01, 0.005);
					}, 0.01);
				}, 0.05);
			});
		});

		context("Tone", function(){

			afterEach(function(){
				if (Tone.context.state !== 'closed'){
					//reset the context
					return Tone.context.dispose().then(function(){
						Tone.context = new Context();
					})
				}
			});

			it ("has a context", function(){
				expect(Tone.context).to.exist;
				expect(Tone.context).to.be.instanceOf(Context);
			});

			it ("can set a new context", function(){
				Tone.context = new Context();
			});

			it ("invokes the ready promise", function(){
				return Tone.context.ready();
			});

			it ("invokes init when a new context is set", function(done){
				this.timeout(200);
				var initFn = function(context){
					expect(Tone.context).to.equal(context);
					Context.off("init", initFn);
					done();
				};
				Context.on("init", initFn);
				Tone.context = new Context();
			});

			it ("invokes close when a context is disposed", function(done){
				this.timeout(200);
				var closeFn = function(context){
					expect(context).to.be.instanceOf(Context);
					Context.off("close", closeFn);
					//set a new context
					Tone.context = new Context();
					done();
				};
				Context.on("close", closeFn);
				Tone.context.dispose();
			});
		});

		context("get/set", function(){

			var ctx;
			beforeEach(function(){
				ctx = new Context();
				return ctx.ready();
			});

			afterEach(function(){
				return ctx.dispose();
			});

			it ("can set the lookAhead", function(){
				ctx.lookAhead = 0.05;
				expect(ctx.lookAhead).to.equal(0.05);
			});

			it ("can set the updateInterval", function(){
				ctx.updateInterval = 0.05;
				expect(ctx.updateInterval).to.equal(0.05);
			});

			it ("can set the latencyHint", function(){
				ctx.latencyHint = "fastest";
				expect(ctx.latencyHint).to.equal("fastest");
				expect(ctx.lookAhead).to.be.closeTo(0.01, 0.05);
				expect(ctx.updateInterval).to.be.closeTo(0.01, 0.05);
				// test all other latency hints
				var latencyHints = ["interactive", "playback", "balanced", 0.2]
				latencyHints.forEach(function(hint){
					ctx.latencyHint = hint;
					expect(ctx.latencyHint).to.equal(hint);
				});
			});

			it ("gets a constant signal", function(){
				var bufferSrc = ctx.getConstant(1);
				expect(bufferSrc).is.instanceOf(AudioBufferSourceNode);
				var buffer = bufferSrc.buffer.getChannelData(0);
				for (var i = 0; i < buffer.length; i++){
					expect(buffer[i]).to.equal(1);
				}
			});

			it ("multiple calls return the same buffer source", function(){
				var bufferA = ctx.getConstant(2);
				var bufferB = ctx.getConstant(2);
				expect(bufferA).to.equal(bufferB);
			});

		});
	});

});
<commit_msg>testing that dispose returns a Promise<commit_after>define(["Test", "Tone/core/Context", "Tone/core/Tone", "helper/Offline"],
	function (Test, Context, Tone, Offline) {

	describe("Context", function(){

		context("AudioContext", function(){

			it ("extends the AudioContext methods", function(){
				var ctx = new Context();
				expect(ctx).to.have.property("createGain");
				expect(ctx.createGain()).to.be.instanceOf(GainNode);
				expect(ctx).to.have.property("createOscillator");
				expect(ctx.createOscillator()).to.be.instanceOf(OscillatorNode);
				expect(ctx).to.have.property("createDelay");
				expect(ctx.createDelay()).to.be.instanceOf(DelayNode);
				return ctx.dispose();
			});

			it ("clock is running", function(done){
				var interval = setInterval(function(){
					if (Tone.context.currentTime > 0.5){
						clearInterval(interval);
						done();
					}
				}, 20);
			});

			it ("can be created an disposed", function(){
				var ctx = new Context();
				return ctx.dispose().then(function(){
					Test.wasDisposed(ctx);
				});
			});

			it ("'dispose' returns a promise which resolves", function(){
				var ctx = new Context();
				var promise = ctx.dispose();
				expect(promise).to.be.instanceOf(Promise);
				return promise;
			});

			it ("can be constructed with an options object", function(){
				var ctx = new Context({
					"lookAhead" : 0.2,
					"latencyHint" : "fastest",
					"clockSource" : "timeout"
				});
				expect(ctx.lookAhead).to.equal(0.2);
				expect(ctx.latencyHint).to.equal("fastest");
				expect(ctx.clockSource).to.equal("timeout");
				return ctx.dispose();
			});
		});

		context("clockSource", function(){

			var ctx;
			beforeEach(function(){
				ctx = new Context();
				return ctx.ready();
			});

			afterEach(function(){
				return ctx.dispose();
			});

			it ("defaults to 'worker'", function(){
				expect(ctx.clockSource).to.equal("worker");
			});

			it ("provides callback", function(done){
				expect(ctx.clockSource).to.equal("worker");
				ctx.setTimeout(function(){
					done();
				}, 0.1);
			});

			it ("can be set to 'timeout'", function(done){
				ctx.clockSource = "timeout";
				expect(ctx.clockSource).to.equal("timeout");
				ctx.setTimeout(function(){
					done();
				}, 0.1);
			});

			it ("can be set to 'offline'", function(done){
				ctx.clockSource = "offline";
				expect(ctx.clockSource).to.equal("offline");
				//provides no callback
				ctx.setTimeout(function(){
					throw new Error("shouldn't be called");
				}, 0.1);
				setTimeout(function(){
					done();
				}, 200);
			});
		});

		context("setTimeout", function(){

			var ctx;
			beforeEach(function(){
				ctx = new Context();
				return ctx.ready();
			});

			afterEach(function(){
				return ctx.dispose();
			});

			it ("can set a timeout", function(done){
				ctx.setTimeout(function(){
					done();
				}, 0.1);
			});

			it ("returns an id", function(){
				expect(ctx.setTimeout(function(){}, 0.1)).to.be.a("number");
				//try clearing a random ID, shouldn't cause any errors
				ctx.clearTimeout(-2);
			});

			it ("timeout is not invoked when cancelled", function(done){
				var id = ctx.setTimeout(function(){
					throw new Error("shouldn't be invoked");
				}, 0.01);
				ctx.clearTimeout(id);
				ctx.setTimeout(function(){
					done();
				}, 0.02);
			});

			it ("order is maintained", function(done){
				var wasInvoked = false;
				ctx.setTimeout(function(){
					expect(wasInvoked).to.be.true;
					done();
				}, 0.011);
				ctx.setTimeout(function(){
					wasInvoked = true;
				}, 0.01);
			});

			it ("is invoked in the offline context", function(){
				return Offline(function(Transport){
					Transport.context.setTimeout(function(){
						expect(Tone.now()).to.be.closeTo(0.01, 0.005);
					}, 0.01);
				}, 0.05);
			});
		});

		context("Tone", function(){

			afterEach(function(){
				if (Tone.context.state !== 'closed'){
					//reset the context
					return Tone.context.dispose().then(function(){
						Tone.context = new Context();
					})
				}
			});

			it ("has a context", function(){
				expect(Tone.context).to.exist;
				expect(Tone.context).to.be.instanceOf(Context);
			});

			it ("can set a new context", function(){
				Tone.context = new Context();
			});

			it ("invokes the ready promise", function(){
				return Tone.context.ready();
			});

			it ("invokes init when a new context is set", function(done){
				this.timeout(200);
				var initFn = function(context){
					expect(Tone.context).to.equal(context);
					Context.off("init", initFn);
					done();
				};
				Context.on("init", initFn);
				Tone.context = new Context();
			});

			it ("invokes close when a context is disposed", function(done){
				this.timeout(200);
				var closeFn = function(context){
					expect(context).to.be.instanceOf(Context);
					Context.off("close", closeFn);
					//set a new context
					Tone.context = new Context();
					done();
				};
				Context.on("close", closeFn);
				Tone.context.dispose();
			});
		});

		context("get/set", function(){

			var ctx;
			beforeEach(function(){
				ctx = new Context();
				return ctx.ready();
			});

			afterEach(function(){
				return ctx.dispose();
			});

			it ("can set the lookAhead", function(){
				ctx.lookAhead = 0.05;
				expect(ctx.lookAhead).to.equal(0.05);
			});

			it ("can set the updateInterval", function(){
				ctx.updateInterval = 0.05;
				expect(ctx.updateInterval).to.equal(0.05);
			});

			it ("can set the latencyHint", function(){
				ctx.latencyHint = "fastest";
				expect(ctx.latencyHint).to.equal("fastest");
				expect(ctx.lookAhead).to.be.closeTo(0.01, 0.05);
				expect(ctx.updateInterval).to.be.closeTo(0.01, 0.05);
				// test all other latency hints
				var latencyHints = ["interactive", "playback", "balanced", 0.2]
				latencyHints.forEach(function(hint){
					ctx.latencyHint = hint;
					expect(ctx.latencyHint).to.equal(hint);
				});
			});

			it ("gets a constant signal", function(){
				var bufferSrc = ctx.getConstant(1);
				expect(bufferSrc).is.instanceOf(AudioBufferSourceNode);
				var buffer = bufferSrc.buffer.getChannelData(0);
				for (var i = 0; i < buffer.length; i++){
					expect(buffer[i]).to.equal(1);
				}
			});

			it ("multiple calls return the same buffer source", function(){
				var bufferA = ctx.getConstant(2);
				var bufferB = ctx.getConstant(2);
				expect(bufferA).to.equal(bufferB);
			});

		});
	});

});
<|endoftext|>


<commit_before>/**
 * DashboardSharingSettings Footer component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { useCallback } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Link from '../../Link';
import Button from '../../Button';
import Notice from './Notice';

export default function Footer( { closeDialog } ) {
	const onApply = useCallback( () => {
		// @TODO: Implement Apply behaviour.
	}, [] );

	return (

				<Notice />

				<Link onClick={ closeDialog }>
					{ __( 'Cancel', 'google-site-kit' ) }
				</Link>

				<Button onClick={ onApply }>
					{ __( 'Apply', 'google-site-kit' ) }
				</Button>

	);
}

Footer.propTypes = {
	closeDialog: PropTypes.func.isRequired,
};
<commit_msg>Disable apply button if there is not settings changed.<commit_after>/**
 * DashboardSharingSettings Footer component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { useCallback } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import Link from '../../Link';
import Button from '../../Button';
import Notice from './Notice';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
const { useSelect } = Data;

export default function Footer( { closeDialog } ) {
	const canSubmitSharingChanges = useSelect( ( select ) =>
		select( CORE_MODULES ).canSubmitSharingChanges()
	);
	const onApply = useCallback( () => {
		// @TODO: Implement Apply behaviour.
	}, [] );

	return (

				<Notice />

				<Link onClick={ closeDialog }>
					{ __( 'Cancel', 'google-site-kit' ) }
				</Link>

				<Button
					onClick={ onApply }
					disabled={ ! canSubmitSharingChanges }
				>
					{ __( 'Apply', 'google-site-kit' ) }
				</Button>

	);
}

Footer.propTypes = {
	closeDialog: PropTypes.func.isRequired,
};
<|endoftext|>


<commit_before>/**
 * DashboardSharingSettings Module component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Tooltip } from '@material-ui/core';

/**
 * WordPress dependencies
 */
import { __, sprintf } from '@wordpress/i18n';
import { Icon, info } from '@wordpress/icons';
import {
	createInterpolateElement,
	useCallback,
	useEffect,
	useState,
} from '@wordpress/element';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import ModuleIcon from '../../ModuleIcon';
import UserRoleSelect from '../UserRoleSelect';
import { Select } from '../../../material-components';
import useViewContext from '../../../hooks/useViewContext';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { CORE_SITE } from '../../../googlesitekit/datastore/site/constants';
import { CORE_UI } from '../../../googlesitekit/datastore/ui/constants';
import { EDITING_MANAGEMENT_KEY, SHARING_SETTINGS_SLUG_KEY } from './constants';
import { trackEvent } from '../../../util';
import {
	CORE_USER,
	PERMISSION_DELEGATE_MODULE_SHARING_MANAGEMENT,
	PERMISSION_MANAGE_MODULE_SHARING_OPTIONS,
} from '../../../googlesitekit/datastore/user/constants';
const { useSelect, useDispatch } = Data;

const viewAccessOptions = [
	{
		value: 'owner',
		label: __( 'Only Me', 'google-site-kit' ),
	},
	{
		value: 'all_admins',
		label: __( 'All Admins', 'google-site-kit' ),
	},
];

export default function Module( { moduleSlug, moduleName, ownerUsername } ) {
	const viewContext = useViewContext();

	const [ manageViewAccess, setManageViewAccess ] = useState( undefined );
	const hasMultipleAdmins = useSelect( ( select ) =>
		select( CORE_SITE ).hasMultipleAdmins()
	);
	const management = useSelect(
		( select ) =>
			select( CORE_MODULES ).getSharingManagement( moduleSlug ) ?? 'owner'
	);
	const hasOwnedModule = useSelect( ( select ) =>
		select( CORE_USER ).hasCapability(
			PERMISSION_DELEGATE_MODULE_SHARING_MANAGEMENT,
			moduleSlug
		)
	);
	const hasSharingCapability = useSelect( ( select ) =>
		select( CORE_USER ).hasCapability(
			PERMISSION_MANAGE_MODULE_SHARING_OPTIONS,
			moduleSlug
		)
	);
	const sharedOwnershipModules = useSelect( ( select ) =>
		select( CORE_MODULES ).getSharedOwnershipModules()
	);
	const editingModuleSlug = useSelect( ( select ) =>
		select( CORE_UI ).getValue( SHARING_SETTINGS_SLUG_KEY )
	);
	const isSaving = useSelect( ( select ) =>
		select( CORE_MODULES ).isDoingSubmitSharingChanges()
	);

	const { setSharingManagement } = useDispatch( CORE_MODULES );
	const { setValue } = useDispatch( CORE_UI );

	const sharedOwnershipModule =
		sharedOwnershipModules &&
		Object.keys( sharedOwnershipModules ).includes( moduleSlug );

	useEffect( () => {
		if ( sharedOwnershipModule ) {
			setManageViewAccess( 'all_admins' );
		} else {
			setManageViewAccess( management );
		}
	}, [ management, sharedOwnershipModule ] );

	const handleOnChange = useCallback(
		( event ) => {
			const value = event.target.value;
			setValue( EDITING_MANAGEMENT_KEY, true );
			setValue( SHARING_SETTINGS_SLUG_KEY, moduleSlug );
			setManageViewAccess( value );
			setSharingManagement( moduleSlug, value );
			trackEvent(
				`${ viewContext }_sharing`,
				`change_management_${ value }`,
				moduleSlug
			);
		},
		[
			moduleSlug,
			viewContext,
			setManageViewAccess,
			setSharingManagement,
			setValue,
		]
	);

	const isLocked =
		( editingModuleSlug !== undefined &&
			moduleSlug !== editingModuleSlug ) ||
		isSaving;
	const isEditing = moduleSlug === editingModuleSlug;

	return (

				<ModuleIcon slug={ moduleSlug } size={ 48 } />

					{ moduleName }

				{ hasSharingCapability && (
					<UserRoleSelect
						moduleSlug={ moduleSlug }
						isLocked={ isLocked }
					/>
				) }

				{ ! hasSharingCapability && (

						{ __(
							'Contact managing user to manage view access',
							'google-site-kit'
						) }

				) }

			{ hasMultipleAdmins && (

					{ hasOwnedModule && (
						<Select
							className="googlesitekit-dashboard-sharing-settings__select"
							value={ manageViewAccess }
							options={ viewAccessOptions }
							onChange={ handleOnChange }
							onClick={ handleOnChange }
							disabled={ sharedOwnershipModule }
							outlined
						/>
					) }

					{ ! hasOwnedModule && ownerUsername && (

							{ createInterpolateElement(
								sprintf(
									/* translators: %s: user who manages the module. */
									__(
										'Managed by  %s',
										'google-site-kit'
									),
									ownerUsername
								),
								{
									span: ,
									strong: ,
								}
							) }

							<Tooltip
								title={ sprintf(
									/* translators: %s: name of the user who manages the module. */
									__(
										'%s has connected this and given managing permissions to all admins. You can change who can view this on the dashboard.',
										'google-site-kit'
									),
									ownerUsername
								) }
								classes={ {
									popper: 'googlesitekit-tooltip-popper',
									tooltip: 'googlesitekit-tooltip',
								} }
							>

									<Icon icon={ info } size={ 18 } />

							</Tooltip>

					) }

			) }

	);
}

Module.propTypes = {
	moduleSlug: PropTypes.string.isRequired,
	moduleName: PropTypes.string.isRequired,
	ownerUsername: PropTypes.string,
};
<commit_msg>Remove uneeded key.<commit_after>/**
 * DashboardSharingSettings Module component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Tooltip } from '@material-ui/core';

/**
 * WordPress dependencies
 */
import { __, sprintf } from '@wordpress/i18n';
import { Icon, info } from '@wordpress/icons';
import {
	createInterpolateElement,
	useCallback,
	useEffect,
	useState,
} from '@wordpress/element';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import ModuleIcon from '../../ModuleIcon';
import UserRoleSelect from '../UserRoleSelect';
import { Select } from '../../../material-components';
import useViewContext from '../../../hooks/useViewContext';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { CORE_SITE } from '../../../googlesitekit/datastore/site/constants';
import { CORE_UI } from '../../../googlesitekit/datastore/ui/constants';
import { EDITING_MANAGEMENT_KEY, SHARING_SETTINGS_SLUG_KEY } from './constants';
import { trackEvent } from '../../../util';
import {
	CORE_USER,
	PERMISSION_DELEGATE_MODULE_SHARING_MANAGEMENT,
	PERMISSION_MANAGE_MODULE_SHARING_OPTIONS,
} from '../../../googlesitekit/datastore/user/constants';
const { useSelect, useDispatch } = Data;

const viewAccessOptions = [
	{
		value: 'owner',
		label: __( 'Only Me', 'google-site-kit' ),
	},
	{
		value: 'all_admins',
		label: __( 'All Admins', 'google-site-kit' ),
	},
];

export default function Module( { moduleSlug, moduleName, ownerUsername } ) {
	const viewContext = useViewContext();

	const [ manageViewAccess, setManageViewAccess ] = useState( undefined );
	const hasMultipleAdmins = useSelect( ( select ) =>
		select( CORE_SITE ).hasMultipleAdmins()
	);
	const management = useSelect(
		( select ) =>
			select( CORE_MODULES ).getSharingManagement( moduleSlug ) ?? 'owner'
	);
	const hasOwnedModule = useSelect( ( select ) =>
		select( CORE_USER ).hasCapability(
			PERMISSION_DELEGATE_MODULE_SHARING_MANAGEMENT,
			moduleSlug
		)
	);
	const hasSharingCapability = useSelect( ( select ) =>
		select( CORE_USER ).hasCapability(
			PERMISSION_MANAGE_MODULE_SHARING_OPTIONS,
			moduleSlug
		)
	);
	const sharedOwnershipModules = useSelect( ( select ) =>
		select( CORE_MODULES ).getSharedOwnershipModules()
	);
	const editingModuleSlug = useSelect( ( select ) =>
		select( CORE_UI ).getValue( SHARING_SETTINGS_SLUG_KEY )
	);
	const isSaving = useSelect( ( select ) =>
		select( CORE_MODULES ).isDoingSubmitSharingChanges()
	);

	const { setSharingManagement } = useDispatch( CORE_MODULES );
	const { setValue } = useDispatch( CORE_UI );

	const sharedOwnershipModule =
		sharedOwnershipModules &&
		Object.keys( sharedOwnershipModules ).includes( moduleSlug );

	useEffect( () => {
		if ( sharedOwnershipModule ) {
			setManageViewAccess( 'all_admins' );
		} else {
			setManageViewAccess( management );
		}
	}, [ management, sharedOwnershipModule ] );

	const handleOnChange = useCallback(
		( event ) => {
			const value = event.target.value;
			setValue( EDITING_MANAGEMENT_KEY, true );
			setValue( SHARING_SETTINGS_SLUG_KEY, moduleSlug );
			setManageViewAccess( value );
			setSharingManagement( moduleSlug, value );
			trackEvent(
				`${ viewContext }_sharing`,
				`change_management_${ value }`,
				moduleSlug
			);
		},
		[
			moduleSlug,
			viewContext,
			setManageViewAccess,
			setSharingManagement,
			setValue,
		]
	);

	const isLocked =
		( editingModuleSlug !== undefined &&
			moduleSlug !== editingModuleSlug ) ||
		isSaving;
	const isEditing = moduleSlug === editingModuleSlug;

	return (

				<ModuleIcon slug={ moduleSlug } size={ 48 } />

					{ moduleName }

				{ hasSharingCapability && (
					<UserRoleSelect
						moduleSlug={ moduleSlug }
						isLocked={ isLocked }
					/>
				) }

				{ ! hasSharingCapability && (

						{ __(
							'Contact managing user to manage view access',
							'google-site-kit'
						) }

				) }

			{ hasMultipleAdmins && (

					{ hasOwnedModule && (
						<Select
							className="googlesitekit-dashboard-sharing-settings__select"
							value={ manageViewAccess }
							options={ viewAccessOptions }
							onChange={ handleOnChange }
							onClick={ handleOnChange }
							disabled={ sharedOwnershipModule }
							outlined
						/>
					) }

					{ ! hasOwnedModule && ownerUsername && (

							{ createInterpolateElement(
								sprintf(
									/* translators: %s: user who manages the module. */
									__(
										'Managed by %s',
										'google-site-kit'
									),
									ownerUsername
								),
								{
									span: ,
									strong: ,
								}
							) }

							<Tooltip
								title={ sprintf(
									/* translators: %s: name of the user who manages the module. */
									__(
										'%s has connected this and given managing permissions to all admins. You can change who can view this on the dashboard.',
										'google-site-kit'
									),
									ownerUsername
								) }
								classes={ {
									popper: 'googlesitekit-tooltip-popper',
									tooltip: 'googlesitekit-tooltip',
								} }
							>

									<Icon icon={ info } size={ 18 } />

							</Tooltip>

					) }

			) }

	);
}

Module.propTypes = {
	moduleSlug: PropTypes.string.isRequired,
	moduleName: PropTypes.string.isRequired,
	ownerUsername: PropTypes.string,
};
<|endoftext|>


<commit_before>/**
 * AMP Container Select component tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import AMPContainerSelect from './AMPContainerSelect';
import { fireEvent, render } from '../../../../../../tests/js/test-utils';
import { STORE_NAME, CONTEXT_WEB, CONTEXT_AMP, CONTAINER_CREATE } from '../../datastore/constants';
import { STORE_NAME as CORE_SITE, AMP_MODE_PRIMARY, AMP_MODE_SECONDARY } from '../../../../googlesitekit/datastore/site/constants';
import { createTestRegistry, freezeFetch } from '../../../../../../tests/js/utils';
import * as factories from '../../datastore/__factories__';

describe( 'AMPContainerSelect', () => {
	let registry;
	beforeEach( () => {
		registry = createTestRegistry();
		// Set settings to prevent fetch in resolver.
		registry.dispatch( STORE_NAME ).setSettings( {} );
		// Set set no existing tag.
		registry.dispatch( STORE_NAME ).receiveGetExistingTag( null );
		// Set site info to prevent error in resolver.
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: AMP_MODE_PRIMARY } );
	} );

	it( 'should render an option for each AMP container of the currently selected account.', () => {
		const account = factories.accountBuilder();
		const webContainers = factories.buildContainers(
			3, { accountId: account.accountId, usageContext: [ CONTEXT_WEB ] }
		);
		const ampContainers = factories.buildContainers(
			3, { accountId: account.accountId, usageContext: [ CONTEXT_AMP ] }
		);
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( [ ...webContainers, ...ampContainers ], { accountID } );

		const { getAllByRole } = render( <AMPContainerSelect />, { registry } );

		const listItems = getAllByRole( 'menuitem', { hidden: true } );
		// Note: we do length + 1 here because there should also be an item for
		// "Set up a new container".
		expect( listItems ).toHaveLength( webContainers.length + 1 );
		expect(
			listItems.some( ( { dataset } ) => dataset.value === CONTAINER_CREATE )
		).toBe( true );
	} );

	it( 'should have a "Set up a new container" item at the end of the list', () => {
		const { account, containers } = factories.buildAccountWithContainers(
			{ container: { usageContext: [ CONTEXT_WEB ] } }
		);
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );

		const { getAllByRole } = render( <AMPContainerSelect />, { registry } );

		const listItems = getAllByRole( 'menuitem', { hidden: true } );
		expect( listItems.pop() ).toHaveTextContent( /set up a new container/i );
	} );

	it( 'should update the container ID and internal container ID when selected', () => {
		const { account, containers } = factories.buildAccountWithContainers(
			{ container: { usageContext: [ CONTEXT_AMP ] } }
		);
		const ampContainer = containers[ 0 ];
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );

		const { container, getByText } = render( <AMPContainerSelect />, { registry } );

		expect( registry.select( STORE_NAME ).getAMPContainerID() ).toBeFalsy();
		expect( registry.select( STORE_NAME ).getInternalAMPContainerID() ).toBeFalsy();

		fireEvent.click( container.querySelector( '.mdc-select__selected-text' ) );
		fireEvent.click( getByText( ampContainer.name ) );

		expect( registry.select( STORE_NAME ).getAMPContainerID() ).toBe( ampContainer.publicId );
		expect( registry.select( STORE_NAME ).getInternalAMPContainerID() ).toBe( ampContainer.containerId );
	} );

	it( 'should render a loading state while accounts have not been loaded', () => {
		freezeFetch( /^\/google-site-kit\/v1\/modules\/tagmanager\/data\/accounts/ );

		const { queryByRole } = render( <AMPContainerSelect />, { registry } );

		expect( queryByRole( 'menu', { hidden: true } ) ).not.toBeInTheDocument();
		expect( queryByRole( 'progressbar' ) ).toBeInTheDocument();
	} );

	it( 'should render a loading state while containers are loading', () => {
		freezeFetch( /^\/google-site-kit\/v1\/modules\/tagmanager\/data\/accounts/ );
		const account = factories.accountBuilder();
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).setAccountID( accountID );

		const { queryByRole } = render( <AMPContainerSelect />, { registry } );

		expect( queryByRole( 'menu', { hidden: true } ) ).not.toBeInTheDocument();
		expect( queryByRole( 'progressbar' ) ).toBeInTheDocument();
	} );

	it( 'should be labeled as "Container" in a primary AMP context', () => {
		const { account, containers } = factories.buildAccountWithContainers();
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: AMP_MODE_PRIMARY } );

		const { container } = render( <AMPContainerSelect />, { registry } );

		expect( container.querySelector( '.mdc-floating-label' ) ).toHaveTextContent( /Container/i );
	} );

	it( 'should be labeled as "AMP Container" in a secondary AMP context', () => {
		const { account, containers } = factories.buildAccountWithContainers();
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: AMP_MODE_SECONDARY } );

		const { container } = render( <AMPContainerSelect />, { registry } );

		expect( container.querySelector( '.mdc-floating-label' ) ).toHaveTextContent( /AMP Container/i );
	} );

	it( 'should render nothing in a non-AMP context', () => {
		const account = factories.accountBuilder();
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: false } );

		const { queryByRole } = render( <AMPContainerSelect />, { registry } );

		expect( queryByRole( 'progressbar' ) ).not.toBeInTheDocument();
		expect( queryByRole( 'menu', { hidden: true } ) ).not.toBeInTheDocument();
	} );
} );
<commit_msg>Fix frozen request in amp container test.<commit_after>/**
 * AMP Container Select component tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import AMPContainerSelect from './AMPContainerSelect';
import { fireEvent, render } from '../../../../../../tests/js/test-utils';
import { STORE_NAME, CONTEXT_WEB, CONTEXT_AMP, CONTAINER_CREATE } from '../../datastore/constants';
import { STORE_NAME as CORE_SITE, AMP_MODE_PRIMARY, AMP_MODE_SECONDARY } from '../../../../googlesitekit/datastore/site/constants';
import { createTestRegistry, freezeFetch } from '../../../../../../tests/js/utils';
import * as factories from '../../datastore/__factories__';

describe( 'AMPContainerSelect', () => {
	let registry;
	beforeEach( () => {
		registry = createTestRegistry();
		// Set settings to prevent fetch in resolver.
		registry.dispatch( STORE_NAME ).setSettings( {} );
		// Set set no existing tag.
		registry.dispatch( STORE_NAME ).receiveGetExistingTag( null );
		// Set site info to prevent error in resolver.
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: AMP_MODE_PRIMARY } );
	} );

	it( 'should render an option for each AMP container of the currently selected account.', () => {
		const account = factories.accountBuilder();
		const webContainers = factories.buildContainers(
			3, { accountId: account.accountId, usageContext: [ CONTEXT_WEB ] }
		);
		const ampContainers = factories.buildContainers(
			3, { accountId: account.accountId, usageContext: [ CONTEXT_AMP ] }
		);
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( [ ...webContainers, ...ampContainers ], { accountID } );

		const { getAllByRole } = render( <AMPContainerSelect />, { registry } );

		const listItems = getAllByRole( 'menuitem', { hidden: true } );
		// Note: we do length + 1 here because there should also be an item for
		// "Set up a new container".
		expect( listItems ).toHaveLength( webContainers.length + 1 );
		expect(
			listItems.some( ( { dataset } ) => dataset.value === CONTAINER_CREATE )
		).toBe( true );
	} );

	it( 'should have a "Set up a new container" item at the end of the list', () => {
		const { account, containers } = factories.buildAccountWithContainers(
			{ container: { usageContext: [ CONTEXT_WEB ] } }
		);
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );

		const { getAllByRole } = render( <AMPContainerSelect />, { registry } );

		const listItems = getAllByRole( 'menuitem', { hidden: true } );
		expect( listItems.pop() ).toHaveTextContent( /set up a new container/i );
	} );

	it( 'should update the container ID and internal container ID when selected', () => {
		const { account, containers } = factories.buildAccountWithContainers(
			{ container: { usageContext: [ CONTEXT_AMP ] } }
		);
		const ampContainer = containers[ 0 ];
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );

		const { container, getByText } = render( <AMPContainerSelect />, { registry } );

		expect( registry.select( STORE_NAME ).getAMPContainerID() ).toBeFalsy();
		expect( registry.select( STORE_NAME ).getInternalAMPContainerID() ).toBeFalsy();

		fireEvent.click( container.querySelector( '.mdc-select__selected-text' ) );
		fireEvent.click( getByText( ampContainer.name ) );

		expect( registry.select( STORE_NAME ).getAMPContainerID() ).toBe( ampContainer.publicId );
		expect( registry.select( STORE_NAME ).getInternalAMPContainerID() ).toBe( ampContainer.containerId );
	} );

	it( 'should render a loading state while accounts have not been loaded', () => {
		freezeFetch( /^\/google-site-kit\/v1\/modules\/tagmanager\/data\/accounts/ );

		const { queryByRole } = render( <AMPContainerSelect />, { registry } );

		expect( queryByRole( 'menu', { hidden: true } ) ).not.toBeInTheDocument();
		expect( queryByRole( 'progressbar' ) ).toBeInTheDocument();
	} );

	it( 'should render a loading state while containers are loading', () => {
		freezeFetch( /^\/google-site-kit\/v1\/modules\/tagmanager\/data\/containers/ );
		const account = factories.accountBuilder();
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).setAccountID( accountID );

		const { queryByRole } = render( <AMPContainerSelect />, { registry } );

		expect( queryByRole( 'menu', { hidden: true } ) ).not.toBeInTheDocument();
		expect( queryByRole( 'progressbar' ) ).toBeInTheDocument();
	} );

	it( 'should be labeled as "Container" in a primary AMP context', () => {
		const { account, containers } = factories.buildAccountWithContainers();
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: AMP_MODE_PRIMARY } );

		const { container } = render( <AMPContainerSelect />, { registry } );

		expect( container.querySelector( '.mdc-floating-label' ) ).toHaveTextContent( /Container/i );
	} );

	it( 'should be labeled as "AMP Container" in a secondary AMP context', () => {
		const { account, containers } = factories.buildAccountWithContainers();
		const accountID = account.accountId;
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: AMP_MODE_SECONDARY } );

		const { container } = render( <AMPContainerSelect />, { registry } );

		expect( container.querySelector( '.mdc-floating-label' ) ).toHaveTextContent( /AMP Container/i );
	} );

	it( 'should render nothing in a non-AMP context', () => {
		const account = factories.accountBuilder();
		registry.dispatch( STORE_NAME ).receiveGetAccounts( [ account ] );
		registry.dispatch( CORE_SITE ).receiveSiteInfo( { ampMode: false } );

		const { queryByRole } = render( <AMPContainerSelect />, { registry } );

		expect( queryByRole( 'progressbar' ) ).not.toBeInTheDocument();
		expect( queryByRole( 'menu', { hidden: true } ) ).not.toBeInTheDocument();
	} );
} );
<|endoftext|>


<commit_before>/* jslint asi:true, browser:true */

if (typeof Lexis == "undefined") var Lexis = {};

var Dictionary = {
    description: null,
    element: null,
    filter: function() {
        var i = 0;
        var current_element = null;
        for (i = 0; i < Dictionary.element.childElementCount; i++) {
            current_element = Dictionary.element.children.item(i);
            if (current_element.tagName !== "SECTION") continue;
            if (window.location.hash && (window.location.hash === "#:" || (window.location.hash.indexOf(":") === -1 && Dictionary.lemmaContainsForm(Dictionary.getLemmaFromId(current_element.id), window.location.hash.substr(1))) || current_element.id === window.location.hash.substr(1))) current_element.removeAttribute("hidden");
            else current_element.setAttribute("hidden", "");
        }
    },
    getHTML: function(e) {
        var i = 0;
        var s = "";
        var current_node = null;
        for (i = 0; i < e.childNodes.length; i++) {
            current_node = e.childNodes.item(i);
            if (current_node.nodeType === 3) {
                s += current_node.textContent;
            }
            else if (current_node.nodeType === 1) {
                switch (current_node.tagName) {
                    case "wordref":
                        s += "" + current_node.textContent + "";
                        break;
                    case "etymon":
                    case "mention":
                        if (current_node.hasAttributeNS("http://www.w3.org/XML/1998/namespace", "lang")) s += "" + current_node.textContent + "";
                        else s += "" + current_node.textContent + "";
                        break;
                }
            }
        }
        return s;
    },
    getHumanReadableWordClass: function(word_class) {
        word_class = Dictionary.string(word_class);
        var class_list = word_class.split(" ");
        var class_item_list = null;
        var i = 0;
        var j = 0;
        var s = "";
        var q = null;
        for (i = 0; i < class_list.length; i++) {
            if (i) s += ", ";
            class_item_list = class_list[i].split(".");
            q = {};
            for (j = 0; j < class_item_list.length; j++) {
                q[class_item_list[j]] = true;
            }

            if (q.pfv) s += "perfective ";
            if (q.prog) s += "progressive ";
            if (q.cont) s += "continuous ";
            if (q["1"]) s += "first-person ";
            if (q["2"]) s += "second-person ";
            if (q["3"]) s += "third-person ";
            if (q.masc) s += "masculine ";
            if (q.fem) s += "feminine ";
            if (q.neut) s += "neuter ";
            if (q.sing) s += "singular ";
            if (q.pl) s += "plural ";
            if (q.inf) s += "infinitive ";
            if (q.mod) {
                if (q.mir) s += "mirative ";
                else if (q.opt) s += "optative ";
                else if (q.hort) s += "hortative ";
                else if (q.perm) s += "permissive ";
                else if (q.jus) s += "jussive ";
                else if (q.int) s += "interrogative ";
                else s += "modal ";
            }
            if (q.det) {
                if (q.qnt) s += "quantifier ";
                else if (q.num) s += "numeral ";
                else if (q.dem) s += "demonstrative ";
                else s += "determiner ";
            }
            if (q.post) s += "post-position ";
            if (q.ptcl) s += "particle ";
            if (q.adj) s += "adjective ";
            if (q.adv) s += "adverb ";
            if (q.pron) s += "pronoun ";
            if (q.n) s += "noun ";
            if (q.v) s += "verb ";
            s = s.trim();
        }
        return s;
    },
    getLemmaFromId: function(id) {
        return Dictionary.lemmas[id.split(":")[0]][Number(id.split(":")[1])]
    },
    getLemmaName: function(lemma) {
        return Dictionary.string(lemma).replace(/[:'"#\?]/g, "-");
    },
    ids: [],
    init: function() {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.addEventListener("load", function() {Dictionary.load(document.documentElement.dataset.src + "?" + new Date().getTime())}, false);
        document.head.appendChild(script);
        script.src = "http://benjam.faint.xyz/langdev/!DATA!/master/!DATA!/LexisML/scripts/normalize.js";
    },
    lang: "",
    lemmaContainsForm: function(lemma, form) {
        var i;
        for (i = 0; i < lemma.forms.length; i++) {
            if (Dictionary.getLemmaName(lemma.forms.item(i).textContent) == form) return true;
        }
        return false;
    },
    lemmas: {},
    lexis: undefined,
    load: function(src) {
        Dictionary.Requester.addEventListener("load", Dictionary.setup, false);
        Dictionary.Requester.open("get", src, true);
        Dictionary.Requester.overrideMimeType("text/xml");
        Dictionary.Requester.send();
    },
    Requester: new XMLHttpRequest(),
    setup: function() {
        Dictionary.lang = document.documentElement.lang;
        Dictionary.lexis = Dictionary.Requester.responseXML;
        Lexis.normalize(Dictionary.lexis);
        var i = 0;
        var j = 0;
        var elements = Dictionary.lexis.documentElement.childNodes;
        var current_element;
        var current_lemma;
        var lemma_name;
        var lemma_id;
        var class_value;
        var article_html;
        var section_html;
        for (i = 0; i < elements.length; i++) {
            if (elements.item(i).nodeType !== 1) continue;
            current_element = elements.item(i);
            switch (current_element.tagName) {
                case "meta":
                    switch (current_element.getAttribute("name")) {
                        case "title":
                            Dictionary.title = [current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"), current_element.textContent];
                            break;
                        case "description":
                            Dictionary.description = [current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"), Dictionary.getHTML(current_element)];
                            break;
                        case "splash":
                            Dictionary.splashes[Dictionary.splashes.length] = [current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"), Dictionary.getHTML(current_element)];
                            break;
                    }
                    break;
                case "affix":
                case "word":
                    lemma_name = Dictionary.getLemmaName(current_element.getAttribute("lemma"));
                    if (Dictionary.lemmas[lemma_name] === undefined) Dictionary.lemmas[lemma_name] = [];
                    lemma_id = lemma_name + ":" + Dictionary.lemmas[lemma_name].length;
                    current_lemma = Dictionary.lemmas[lemma_name][Dictionary.lemmas[lemma_name].length] = {
                        type: current_element.tagName,
                        name: current_element.getAttribute("lemma"),
                        id: lemma_id,
                        word_class: null,
                        lang: current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"),
                        form: null,
                        forms: current_element.getElementsByTagNameNS("http://leaf.faint.xyz/lexisml", "form"),
                        etymology: current_element.getElementsByTagNameNS("http://leaf.faint.xyz/lexisml", "etymology").item(0),
                        meanings: current_element.getElementsByTagNameNS("http://leaf.faint.xyz/lexisml", "meaning")
                    }
                    class_value = "";
                    for (j = 0; j < current_lemma.forms.length; j++) {
                        if (current_lemma.forms.item(j).hasAttribute("class")) class_value += current_lemma.forms.item(j).getAttribute("class") + " ";
                        if (current_lemma.forms.item(j).textContent == current_lemma.name) current_lemma.form = current_lemma.forms.item(j);
                    }
                    if (class_value.trim() !== "") current_lemma.word_class = class_value.trim();
                    Dictionary.ids[Dictionary.ids.length] = lemma_id;
                    break;
            }
        }
        Dictionary.ids.sort(function (a, b) {return String(a).substr(String(a).search(/\w/)).localeCompare(String(b).substr(String(b).search(/\w/)));});
        Dictionary.element = document.createElement("article");
        document.title = Dictionary.title[1];
        document.getElementsByTagName("title").item(0).lang = Dictionary.title[0];
        article_html = "
";
        article_html += "
" + Dictionary.title[1] + "
";
        if (Dictionary.splashes.length) {
            i = Math.floor(Math.random()*Dictionary.splashes.length);
            article_html += "
" + Dictionary.splashes[i][1] + "
";
        }
        if (Dictionary.description !== null) article_html += "

" + Dictionary.description[1] + "
";
        article_html += "
search: <input id='filter-input' type='text' autocomplete='off' autofocus inputmode='latin' placeholder='enter search term…' spellcheck='false'> <button id='filter-button'>ok</button> <button id='unfilter-button'>show everything</button>
";
        article_html += "";
        Dictionary.element.innerHTML = article_html;
        for (i = 0; i < Dictionary.ids.length; i++) {
            current_element = document.createElement("section");
            current_lemma = Dictionary.getLemmaFromId(Dictionary.ids[i]);
            current_element.lang = current_lemma.lang;
            current_element.id = Dictionary.ids[i];
            current_element.setAttribute("hidden", "");
            section_html = "";
            section_html += "
";
            section_html += "<dfn>" + current_lemma.name + "</dfn>"
            class_value = "";
            for (j = 0; j < current_lemma.forms.length; j++) {
                if (current_lemma.forms.item(j) == current_lemma.form) {
                    if (current_lemma.forms.item(j).hasAttribute("pronunciation")) section_html += " " + current_lemma.forms.item(j).getAttribute("pronunciation") + "";
                }
                else {
                    if (class_value === "") class_value += " (";
                    else class_value += ", ";
                    class_value += (Dictionary.getHumanReadableWordClass(current_lemma.forms.item(j).getAttribute("class")) + " <dfn>" + current_lemma.forms.item(j).textContent + "</dfn>").trim();
                    if (current_lemma.forms.item(j).hasAttribute("pronunciation")) class_value += " " + Dictionary.string(current_lemma.forms.item(j).getAttribute("pronunciation")) + "";
                }
            }
            if (class_value !== "") class_value += ")";
            section_html += "
";
            if (current_lemma.type == "word") {
                section_html += "
" + Dictionary.getHumanReadableWordClass(current_lemma.form.getAttribute("class")) + class_value + "
";
            }
            else if (current_lemma.type == "affix") {
                section_html += "
" + (Dictionary.getHumanReadableWordClass(current_lemma.form.getAttribute("class")) + " affix").trim() + class_value + "
";
            }
            section_html += "";
            if (current_lemma.meanings.length > 1) {
                section_html += "
";
                for (j = 0; j < current_lemma.meanings.length; j++) {
                    section_html += "
";
                    if (current_lemma.meanings.item(j).getAttribute("class") != current_lemma.word_class) section_html += "(" + Dictionary.getHumanReadableWordClass(current_lemma.meanings.item(j).getAttribute("class")) + ") ";
                    section_html += Dictionary.getHTML(current_lemma.meanings.item(j));
                    section_html += "
";
                }
                section_html += "
";
            }
            else if (current_lemma.meanings.length == 1) {
                section_html += "
";
                if (current_lemma.meanings.item(0).getAttribute("class") != current_lemma.word_class) section_html += "(" + Dictionary.getHumanReadableWordClass(current_lemma.meanings.item(0).getAttribute("class")) + ") ";
                section_html += Dictionary.getHTML(current_lemma.meanings.item(0));
                section_html += "
";
            }
            if (current_lemma.etymology) section_html += "
etymology

" + Dictionary.getHTML(current_lemma.etymology) + "
";
            current_element.innerHTML = section_html;
            Dictionary.element.appendChild(current_element);
        }
        document.body.textContent = null;
        document.body.appendChild(document.createElement("main").appendChild(Dictionary.element).parentElement);
        document.getElementById("filter-button").addEventListener("click", function(){window.location.hash = Dictionary.getLemmaName(document.getElementById("filter-input").value);} ,false);
        document.getElementById("filter-input").addEventListener("keypress", function(e){if (e.key === "Enter" || e.keyCode === 13) window.location.hash = Dictionary.getLemmaName(document.getElementById("filter-input").value);} ,false);
        document.getElementById("unfilter-button").addEventListener("click", function(){window.location.hash = ":";}, false);
        Dictionary.filter();
    },
    splashes: [],
    string: function(n) {
        if (typeof n == "string") {
            return n;
        }
        else if (typeof n == "number") {
            return String(n);
        }
        return "";
    },
    title: "",
    title_lang: ""
}

window.addEventListener("load", Dictionary.init, false);
window.addEventListener("hashchange", Dictionary.filter, false);
<commit_msg>Added formality<commit_after>/* jslint asi:true, browser:true */

if (typeof Lexis == "undefined") var Lexis = {};

var Dictionary = {
    description: null,
    element: null,
    filter: function() {
        var i = 0;
        var current_element = null;
        for (i = 0; i < Dictionary.element.childElementCount; i++) {
            current_element = Dictionary.element.children.item(i);
            if (current_element.tagName !== "SECTION") continue;
            if (window.location.hash && (window.location.hash === "#:" || (window.location.hash.indexOf(":") === -1 && Dictionary.lemmaContainsForm(Dictionary.getLemmaFromId(current_element.id), window.location.hash.substr(1))) || current_element.id === window.location.hash.substr(1))) current_element.removeAttribute("hidden");
            else current_element.setAttribute("hidden", "");
        }
    },
    getHTML: function(e) {
        var i = 0;
        var s = "";
        var current_node = null;
        for (i = 0; i < e.childNodes.length; i++) {
            current_node = e.childNodes.item(i);
            if (current_node.nodeType === 3) {
                s += current_node.textContent;
            }
            else if (current_node.nodeType === 1) {
                switch (current_node.tagName) {
                    case "wordref":
                        s += "" + current_node.textContent + "";
                        break;
                    case "etymon":
                    case "mention":
                        if (current_node.hasAttributeNS("http://www.w3.org/XML/1998/namespace", "lang")) s += "" + current_node.textContent + "";
                        else s += "" + current_node.textContent + "";
                        break;
                }
            }
        }
        return s;
    },
    getHumanReadableWordClass: function(word_class) {
        word_class = Dictionary.string(word_class);
        var class_list = word_class.split(" ");
        var class_item_list = null;
        var i = 0;
        var j = 0;
        var s = "";
        var q = null;
        for (i = 0; i < class_list.length; i++) {
            if (i) s += ", ";
            class_item_list = class_list[i].split(".");
            q = {};
            for (j = 0; j < class_item_list.length; j++) {
                q[class_item_list[j]] = true;
            }

            if (q.form) s += "formal ";
            if (q.ifrm) s += "informal ";
            if (q.slng) s += "slang ";
            if (q.pfv) s += "perfective ";
            if (q.prog) s += "progressive ";
            if (q.cont) s += "continuous ";
            if (q["1"]) s += "first-person ";
            if (q["2"]) s += "second-person ";
            if (q["3"]) s += "third-person ";
            if (q.masc) s += "masculine ";
            if (q.fem) s += "feminine ";
            if (q.neut) s += "neuter ";
            if (q.sing) s += "singular ";
            if (q.pl) s += "plural ";
            if (q.inf) s += "infinitive ";
            if (q.mod) {
                if (q.mir) s += "mirative ";
                else if (q.opt) s += "optative ";
                else if (q.hort) s += "hortative ";
                else if (q.perm) s += "permissive ";
                else if (q.jus) s += "jussive ";
                else if (q.int) s += "interrogative ";
                else s += "modal ";
            }
            if (q.det) {
                if (q.qnt) s += "quantifier ";
                else if (q.num) s += "numeral ";
                else if (q.dem) s += "demonstrative ";
                else s += "determiner ";
            }
            if (q.post) s += "post-position ";
            if (q.ptcl) s += "particle ";
            if (q.adj) s += "adjective ";
            if (q.adv) s += "adverb ";
            if (q.pron) s += "pronoun ";
            if (q.n) s += "noun ";
            if (q.v) s += "verb ";
            s = s.trim();
        }
        return s;
    },
    getLemmaFromId: function(id) {
        return Dictionary.lemmas[id.split(":")[0]][Number(id.split(":")[1])]
    },
    getLemmaName: function(lemma) {
        return Dictionary.string(lemma).replace(/[:'"#\?]/g, "-");
    },
    ids: [],
    init: function() {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.addEventListener("load", function() {Dictionary.load(document.documentElement.dataset.src + "?" + new Date().getTime())}, false);
        document.head.appendChild(script);
        script.src = "http://benjam.faint.xyz/langdev/!DATA!/master/!DATA!/LexisML/scripts/normalize.js";
    },
    lang: "",
    lemmaContainsForm: function(lemma, form) {
        var i;
        for (i = 0; i < lemma.forms.length; i++) {
            if (Dictionary.getLemmaName(lemma.forms.item(i).textContent) == form) return true;
        }
        return false;
    },
    lemmas: {},
    lexis: undefined,
    load: function(src) {
        Dictionary.Requester.addEventListener("load", Dictionary.setup, false);
        Dictionary.Requester.open("get", src, true);
        Dictionary.Requester.overrideMimeType("text/xml");
        Dictionary.Requester.send();
    },
    Requester: new XMLHttpRequest(),
    setup: function() {
        Dictionary.lang = document.documentElement.lang;
        Dictionary.lexis = Dictionary.Requester.responseXML;
        Lexis.normalize(Dictionary.lexis);
        var i = 0;
        var j = 0;
        var elements = Dictionary.lexis.documentElement.childNodes;
        var current_element;
        var current_lemma;
        var lemma_name;
        var lemma_id;
        var class_value;
        var article_html;
        var section_html;
        for (i = 0; i < elements.length; i++) {
            if (elements.item(i).nodeType !== 1) continue;
            current_element = elements.item(i);
            switch (current_element.tagName) {
                case "meta":
                    switch (current_element.getAttribute("name")) {
                        case "title":
                            Dictionary.title = [current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"), current_element.textContent];
                            break;
                        case "description":
                            Dictionary.description = [current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"), Dictionary.getHTML(current_element)];
                            break;
                        case "splash":
                            Dictionary.splashes[Dictionary.splashes.length] = [current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"), Dictionary.getHTML(current_element)];
                            break;
                    }
                    break;
                case "affix":
                case "word":
                    lemma_name = Dictionary.getLemmaName(current_element.getAttribute("lemma"));
                    if (Dictionary.lemmas[lemma_name] === undefined) Dictionary.lemmas[lemma_name] = [];
                    lemma_id = lemma_name + ":" + Dictionary.lemmas[lemma_name].length;
                    current_lemma = Dictionary.lemmas[lemma_name][Dictionary.lemmas[lemma_name].length] = {
                        type: current_element.tagName,
                        name: current_element.getAttribute("lemma"),
                        id: lemma_id,
                        word_class: null,
                        lang: current_element.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang"),
                        form: null,
                        forms: current_element.getElementsByTagNameNS("http://leaf.faint.xyz/lexisml", "form"),
                        etymology: current_element.getElementsByTagNameNS("http://leaf.faint.xyz/lexisml", "etymology").item(0),
                        meanings: current_element.getElementsByTagNameNS("http://leaf.faint.xyz/lexisml", "meaning")
                    }
                    class_value = "";
                    for (j = 0; j < current_lemma.forms.length; j++) {
                        if (current_lemma.forms.item(j).hasAttribute("class")) class_value += current_lemma.forms.item(j).getAttribute("class") + " ";
                        if (current_lemma.forms.item(j).textContent == current_lemma.name) current_lemma.form = current_lemma.forms.item(j);
                    }
                    if (class_value.trim() !== "") current_lemma.word_class = class_value.trim();
                    Dictionary.ids[Dictionary.ids.length] = lemma_id;
                    break;
            }
        }
        Dictionary.ids.sort(function (a, b) {return String(a).substr(String(a).search(/\w/)).localeCompare(String(b).substr(String(b).search(/\w/)));});
        Dictionary.element = document.createElement("article");
        document.title = Dictionary.title[1];
        document.getElementsByTagName("title").item(0).lang = Dictionary.title[0];
        article_html = "
";
        article_html += "
" + Dictionary.title[1] + "
";
        if (Dictionary.splashes.length) {
            i = Math.floor(Math.random()*Dictionary.splashes.length);
            article_html += "
" + Dictionary.splashes[i][1] + "
";
        }
        if (Dictionary.description !== null) article_html += "

" + Dictionary.description[1] + "
";
        article_html += "
search: <input id='filter-input' type='text' autocomplete='off' autofocus inputmode='latin' placeholder='enter search term…' spellcheck='false'> <button id='filter-button'>ok</button> <button id='unfilter-button'>show everything</button>
";
        article_html += "";
        Dictionary.element.innerHTML = article_html;
        for (i = 0; i < Dictionary.ids.length; i++) {
            current_element = document.createElement("section");
            current_lemma = Dictionary.getLemmaFromId(Dictionary.ids[i]);
            current_element.lang = current_lemma.lang;
            current_element.id = Dictionary.ids[i];
            current_element.setAttribute("hidden", "");
            section_html = "";
            section_html += "
";
            section_html += "<dfn>" + current_lemma.name + "</dfn>"
            class_value = "";
            for (j = 0; j < current_lemma.forms.length; j++) {
                if (current_lemma.forms.item(j) == current_lemma.form) {
                    if (current_lemma.forms.item(j).hasAttribute("pronunciation")) section_html += " " + current_lemma.forms.item(j).getAttribute("pronunciation") + "";
                }
                else {
                    if (class_value === "") class_value += " (";
                    else class_value += ", ";
                    class_value += (Dictionary.getHumanReadableWordClass(current_lemma.forms.item(j).getAttribute("class")) + " <dfn>" + current_lemma.forms.item(j).textContent + "</dfn>").trim();
                    if (current_lemma.forms.item(j).hasAttribute("pronunciation")) class_value += " " + Dictionary.string(current_lemma.forms.item(j).getAttribute("pronunciation")) + "";
                }
            }
            if (class_value !== "") class_value += ")";
            section_html += "
";
            if (current_lemma.type == "word") {
                section_html += "
" + Dictionary.getHumanReadableWordClass(current_lemma.form.getAttribute("class")) + class_value + "
";
            }
            else if (current_lemma.type == "affix") {
                section_html += "
" + (Dictionary.getHumanReadableWordClass(current_lemma.form.getAttribute("class")) + " affix").trim() + class_value + "
";
            }
            section_html += "";
            if (current_lemma.meanings.length > 1) {
                section_html += "
";
                for (j = 0; j < current_lemma.meanings.length; j++) {
                    section_html += "
";
                    if (current_lemma.meanings.item(j).getAttribute("class") != current_lemma.word_class) section_html += "(" + Dictionary.getHumanReadableWordClass(current_lemma.meanings.item(j).getAttribute("class")) + ") ";
                    section_html += Dictionary.getHTML(current_lemma.meanings.item(j));
                    section_html += "
";
                }
                section_html += "
";
            }
            else if (current_lemma.meanings.length == 1) {
                section_html += "
";
                if (current_lemma.meanings.item(0).getAttribute("class") != current_lemma.word_class) section_html += "(" + Dictionary.getHumanReadableWordClass(current_lemma.meanings.item(0).getAttribute("class")) + ") ";
                section_html += Dictionary.getHTML(current_lemma.meanings.item(0));
                section_html += "
";
            }
            if (current_lemma.etymology) section_html += "
etymology

" + Dictionary.getHTML(current_lemma.etymology) + "
";
            current_element.innerHTML = section_html;
            Dictionary.element.appendChild(current_element);
        }
        document.body.textContent = null;
        document.body.appendChild(document.createElement("main").appendChild(Dictionary.element).parentElement);
        document.getElementById("filter-button").addEventListener("click", function(){window.location.hash = Dictionary.getLemmaName(document.getElementById("filter-input").value);} ,false);
        document.getElementById("filter-input").addEventListener("keypress", function(e){if (e.key === "Enter" || e.keyCode === 13) window.location.hash = Dictionary.getLemmaName(document.getElementById("filter-input").value);} ,false);
        document.getElementById("unfilter-button").addEventListener("click", function(){window.location.hash = ":";}, false);
        Dictionary.filter();
    },
    splashes: [],
    string: function(n) {
        if (typeof n == "string") {
            return n;
        }
        else if (typeof n == "number") {
            return String(n);
        }
        return "";
    },
    title: "",
    title_lang: ""
}

window.addEventListener("load", Dictionary.init, false);
window.addEventListener("hashchange", Dictionary.filter, false);
<|endoftext|>


<commit_before>/* global describe, it */
var http = require("http");
var expect = require("unexpected")
  .clone()
  .use(require("unexpected-express"))
  .addAssertion(
    "<object> to yield response <object|string>",
    (expect, subject, value) =>
      expect(subject, "to yield exchange", {
        request: "GET /",
        response: value
      })
  );
var path = require("path");
var express = require("express");
var hijackResponse = require("../lib/hijackResponse");

describe("Express Integration Tests", function() {
  it("simple case", function() {
    var app = express()
      .use(function(req, res, next) {
        hijackResponse(res, function(hijackedResponseBody, res) {
          var chunks = [];
          hijackedResponseBody.on("data", function(chunk) {
            chunks.push(chunk);
          });
          hijackedResponseBody.on("end", function() {
            var result = Buffer.concat(chunks)
              .toString("utf-8")
              .toUpperCase();
            res.write(Buffer.from(result));
            res.end();
          });
        });
        next();
      })
      .use(function(req, res, next) {
        res.setHeader("Content-Type", "text/plain");
        return res.end("foobar");
      });

    return expect(app, "to yield response", {
      statusCode: 200,
      body: "FOOBAR"
    });
  });

  it("simple case altering status code", function() {
    var app = express()
      .use(function(req, res, next) {
        hijackResponse(res, function(hijackedResponseBody, res) {
          var chunks = [];
          hijackedResponseBody.on("data", function(chunk) {
            chunks.push(chunk);
          });
          hijackedResponseBody.on("end", function() {
            res.status(201);
            var result = Buffer.concat(chunks)
              .toString("utf-8")
              .toUpperCase();
            res.write(Buffer.from(result));
            res.end();
          });
        });
        next();
      })
      .use(function(req, res, next) {
        res.setHeader("Content-Type", "text/plain");
        return res.end("foobar");
      });

    return expect(app, "to yield response", {
      statusCode: 201,
      body: "FOOBAR"
    });
  });

  describe("adapted from express-hijackresponse", function() {
    it("Create a test server that pipes the hijacked response into itself, then do a request against it (simple variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            hijackedResponseBody.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        });

      return expect(app, "to yield response", "foo");
    });
    it("Create a test server that pipes the hijacked response into itself, then do a request against it (streaming variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            hijackedResponseBody.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          var num = 0;
          res.setHeader("Content-Type", "text/plain");
          function proceed() {
            if (num < 5) {
              res.write("foo");
              num += 1;
              process.nextTick(proceed);
            } else {
              res.end("bar");
            }
          }
          proceed();
        });

      return expect(app, "to yield response", "foofoofoofoofoobar");
    });
    it("Create a test server that pipes the original response through a buffered stream, then do a request against it (simple variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            var bufferedStream = new (require("bufferedstream"))();
            hijackedResponseBody.pipe(bufferedStream);
            bufferedStream.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        });

      return expect(app, "to yield response", "foo");
    });
    it("Create a test server that pipes the original response through a buffered stream, then do a request against it (streaming variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            var bufferedStream = new (require("bufferedstream"))();
            hijackedResponseBody.pipe(bufferedStream);
            bufferedStream.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.contentType("text/plain");
          res.write("foo");
          res.end("bar");
        });

      return expect(app, "to yield response", "foobar");
    });
    it.skip("Create a test server that hijacks the response and passes an error to next(), then run a request against it", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(res) {
            res.unhijack(function(res) {
              next(new Error("Error!"));
            });
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        })
        .use(require("errorhandler")({ log: false }));

      return expect(app, "to yield response", 500);
    });
    it("Create a test server that hijacks the response and immediately unhijacks it, then run a request against it", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackResponseBody, res) {
            // res.unhijack(true);
            hijackResponseBody.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        });

      return expect(app, "to yield response", "foo");
    });
  });

  it("should work when hijacking a big response body and the compression middleware is present above the hijacking middleware", function() {
    return expect(
      express()
        .use(require("compression")())
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            hijackedResponseBody.pipe(res);
          });
          next();
        })
        .use(express.static(path.resolve(__dirname, "fixtures"))),
      "to yield exchange",
      {
        request: "GET /bigfile.txt",
        response: { body: /^0{1999998}$/ }
      }
    );
  });
  describe("against a real server", function() {
    function createServer(closeSpy) {
      return new Promise(function(resolve) {
        var app = express()
          .use((req, res, next) => {
            res.on("close", () => closeSpy());
            next();
          })
          .use(function(req, res, next) {
            hijackResponse(res, (hijackedResponseBody, res) =>
              hijackedResponseBody.pipe(res)
            );
            next();
          })
          .use(function(req, res) {
            res.write("foo");
          });

        const server = http.Server(app);

        server.listen(0, () => {
          resolve({
            port: server.address().port,
            close: () => server.close()
          });
        });
      });
    }

    it('should not prevent "close" events registered on res from firing when hijacking', async () => {
      let closeCallCount = 0;
      const closeSpy = () => {
        closeCallCount += 1;
      };
      const server = await createServer(closeSpy);

      try {
        await new Promise(function(resolve) {
          var request = http.request({ port: server.port }, res => {
            request.abort();
            setTimeout(() => resolve(), 10);
          });

          request.end();
        });

        expect({ closeCallCount }, "to satisfy", { closeCallCount: 1 });
      } finally {
        server.close();
      }
    });
  });

  describe("against a real proxied server", () => {
    function createApp() {
      return new Promise(resolve => {
        var app = express();

        app.use((req, res, next) => {
          res.status(200);
          res.set("Content-Type", "text/plain");
          res.set("X-Source", "yes!");
          res.end("foo");
        });

        const source = http.Server(app);

        source.listen(0, () => {
          resolve({
            port: source.address().port,
            close: () => source.close()
          });
        });
      });
    }

    function createProxy(sourcePort) {
      return new Promise(resolve => {
        const app = express();

        app.use((req, res, next) => {
          hijackResponse(res, (hijackedResponseBody, res) => {
            res.setHeader("X-Hijacked", "yes!");
            res.setHeader("transfer-encoding", "chunked"); // not set on > 0.10
            res.removeHeader("Content-Length"); // only set on > 0.10
            hijackedResponseBody.pipe(res);
          });
          next();
        });

        app.use(
          require("http-proxy-middleware")({
            target: "http://localhost:" + sourcePort,
            changeOrigin: true,
            logLevel: "silent"
          })
        );

        const proxy = http.Server(app);

        proxy.listen(0, () => {
          resolve({
            port: proxy.address().port,
            close: () => proxy.close()
          });
        });
      });
    }

    function makeRequest(proxyPort) {
      return new Promise(resolve => {
        require("http").get(
          {
            host: "localhost",
            port: proxyPort,
            path: "",
            agent: false
          },
          res => resolve(res)
        );
      });
    }

    it("should not mangle response message", async () => {
      const source = await createApp();
      const proxy = await createProxy(source.port);

      try {
        const res = await makeRequest(proxy.port);

        await expect(res.headers, "to exhaustively satisfy", {
          "x-powered-by": "Express",
          "content-type": /text\/plain/,
          "transfer-encoding": "chunked",
          date: expect.it("to be a string"),
          connection: "close",
          "x-source": "yes!",
          "x-hijacked": "yes!"
        });
      } finally {
        proxy.close();
        source.close();
      }
    });
  });
});
<commit_msg>Remove lying comments<commit_after>/* global describe, it */
var http = require("http");
var expect = require("unexpected")
  .clone()
  .use(require("unexpected-express"))
  .addAssertion(
    "<object> to yield response <object|string>",
    (expect, subject, value) =>
      expect(subject, "to yield exchange", {
        request: "GET /",
        response: value
      })
  );
var path = require("path");
var express = require("express");
var hijackResponse = require("../lib/hijackResponse");

describe("Express Integration Tests", function() {
  it("simple case", function() {
    var app = express()
      .use(function(req, res, next) {
        hijackResponse(res, function(hijackedResponseBody, res) {
          var chunks = [];
          hijackedResponseBody.on("data", function(chunk) {
            chunks.push(chunk);
          });
          hijackedResponseBody.on("end", function() {
            var result = Buffer.concat(chunks)
              .toString("utf-8")
              .toUpperCase();
            res.write(Buffer.from(result));
            res.end();
          });
        });
        next();
      })
      .use(function(req, res, next) {
        res.setHeader("Content-Type", "text/plain");
        return res.end("foobar");
      });

    return expect(app, "to yield response", {
      statusCode: 200,
      body: "FOOBAR"
    });
  });

  it("simple case altering status code", function() {
    var app = express()
      .use(function(req, res, next) {
        hijackResponse(res, function(hijackedResponseBody, res) {
          var chunks = [];
          hijackedResponseBody.on("data", function(chunk) {
            chunks.push(chunk);
          });
          hijackedResponseBody.on("end", function() {
            res.status(201);
            var result = Buffer.concat(chunks)
              .toString("utf-8")
              .toUpperCase();
            res.write(Buffer.from(result));
            res.end();
          });
        });
        next();
      })
      .use(function(req, res, next) {
        res.setHeader("Content-Type", "text/plain");
        return res.end("foobar");
      });

    return expect(app, "to yield response", {
      statusCode: 201,
      body: "FOOBAR"
    });
  });

  describe("adapted from express-hijackresponse", function() {
    it("Create a test server that pipes the hijacked response into itself, then do a request against it (simple variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            hijackedResponseBody.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        });

      return expect(app, "to yield response", "foo");
    });
    it("Create a test server that pipes the hijacked response into itself, then do a request against it (streaming variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            hijackedResponseBody.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          var num = 0;
          res.setHeader("Content-Type", "text/plain");
          function proceed() {
            if (num < 5) {
              res.write("foo");
              num += 1;
              process.nextTick(proceed);
            } else {
              res.end("bar");
            }
          }
          proceed();
        });

      return expect(app, "to yield response", "foofoofoofoofoobar");
    });
    it("Create a test server that pipes the original response through a buffered stream, then do a request against it (simple variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            var bufferedStream = new (require("bufferedstream"))();
            hijackedResponseBody.pipe(bufferedStream);
            bufferedStream.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        });

      return expect(app, "to yield response", "foo");
    });
    it("Create a test server that pipes the original response through a buffered stream, then do a request against it (streaming variant)", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            var bufferedStream = new (require("bufferedstream"))();
            hijackedResponseBody.pipe(bufferedStream);
            bufferedStream.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.contentType("text/plain");
          res.write("foo");
          res.end("bar");
        });

      return expect(app, "to yield response", "foobar");
    });
    it.skip("Create a test server that hijacks the response and passes an error to next(), then run a request against it", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(res) {
            res.unhijack(function(res) {
              next(new Error("Error!"));
            });
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        })
        .use(require("errorhandler")({ log: false }));

      return expect(app, "to yield response", 500);
    });
    it("Create a test server that hijacks the response and immediately unhijacks it, then run a request against it", function() {
      var app = express()
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackResponseBody, res) {
            hijackResponseBody.pipe(res);
          });
          next();
        })
        .use(function(req, res, next) {
          res.send("foo");
        });

      return expect(app, "to yield response", "foo");
    });
  });

  it("should work when hijacking a big response body and the compression middleware is present above the hijacking middleware", function() {
    return expect(
      express()
        .use(require("compression")())
        .use(function(req, res, next) {
          hijackResponse(res, function(hijackedResponseBody, res) {
            hijackedResponseBody.pipe(res);
          });
          next();
        })
        .use(express.static(path.resolve(__dirname, "fixtures"))),
      "to yield exchange",
      {
        request: "GET /bigfile.txt",
        response: { body: /^0{1999998}$/ }
      }
    );
  });
  describe("against a real server", function() {
    function createServer(closeSpy) {
      return new Promise(function(resolve) {
        var app = express()
          .use((req, res, next) => {
            res.on("close", () => closeSpy());
            next();
          })
          .use(function(req, res, next) {
            hijackResponse(res, (hijackedResponseBody, res) =>
              hijackedResponseBody.pipe(res)
            );
            next();
          })
          .use(function(req, res) {
            res.write("foo");
          });

        const server = http.Server(app);

        server.listen(0, () => {
          resolve({
            port: server.address().port,
            close: () => server.close()
          });
        });
      });
    }

    it('should not prevent "close" events registered on res from firing when hijacking', async () => {
      let closeCallCount = 0;
      const closeSpy = () => {
        closeCallCount += 1;
      };
      const server = await createServer(closeSpy);

      try {
        await new Promise(function(resolve) {
          var request = http.request({ port: server.port }, res => {
            request.abort();
            setTimeout(() => resolve(), 10);
          });

          request.end();
        });

        expect({ closeCallCount }, "to satisfy", { closeCallCount: 1 });
      } finally {
        server.close();
      }
    });
  });

  describe("against a real proxied server", () => {
    function createApp() {
      return new Promise(resolve => {
        var app = express();

        app.use((req, res, next) => {
          res.status(200);
          res.set("Content-Type", "text/plain");
          res.set("X-Source", "yes!");
          res.end("foo");
        });

        const source = http.Server(app);

        source.listen(0, () => {
          resolve({
            port: source.address().port,
            close: () => source.close()
          });
        });
      });
    }

    function createProxy(sourcePort) {
      return new Promise(resolve => {
        const app = express();

        app.use((req, res, next) => {
          hijackResponse(res, (hijackedResponseBody, res) => {
            res.setHeader("X-Hijacked", "yes!");
            res.setHeader("transfer-encoding", "chunked");
            res.removeHeader("Content-Length");
            hijackedResponseBody.pipe(res);
          });
          next();
        });

        app.use(
          require("http-proxy-middleware")({
            target: "http://localhost:" + sourcePort,
            changeOrigin: true,
            logLevel: "silent"
          })
        );

        const proxy = http.Server(app);

        proxy.listen(0, () => {
          resolve({
            port: proxy.address().port,
            close: () => proxy.close()
          });
        });
      });
    }

    function makeRequest(proxyPort) {
      return new Promise(resolve => {
        require("http").get(
          {
            host: "localhost",
            port: proxyPort,
            path: "",
            agent: false
          },
          res => resolve(res)
        );
      });
    }

    it("should not mangle response message", async () => {
      const source = await createApp();
      const proxy = await createProxy(source.port);

      try {
        const res = await makeRequest(proxy.port);

        await expect(res.headers, "to exhaustively satisfy", {
          "x-powered-by": "Express",
          "content-type": /text\/plain/,
          "transfer-encoding": "chunked",
          date: expect.it("to be a string"),
          connection: "close",
          "x-source": "yes!",
          "x-hijacked": "yes!"
        });
      } finally {
        proxy.close();
        source.close();
      }
    });
  });
});
<|endoftext|>