<commit_before>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const React = require('React');
const RelayPropTypes = require('../classic/container/RelayPropTypes');

const areEqual = require('areEqual');
const buildReactRelayContainer = require('./buildReactRelayContainer');

const {assertRelayContext} = require('../classic/environment/RelayContext');
const {profileContainer} = require('./ReactRelayContainerProfiler');
const {getContainerName} = require('./ReactRelayContainerUtils');
const {RelayProfiler, isScalarAndEqual} = require('RelayRuntime');

import type {FragmentSpecResolver} from '../classic/environment/RelayCombinedEnvironmentTypes';
import type {RelayEnvironmentInterface as ClassicEnvironment} from '../classic/store/RelayEnvironment';
import type {$RelayProps, GeneratedNodeMap, RelayProp} from './ReactRelayTypes';
import type {
  FragmentMap,
  GraphQLTaggedNode,
  IEnvironment,
  RelayContext,
  Variables,
} from 'RelayRuntime';

type ContainerProps = $FlowFixMeProps;
type ContainerState = {
  data: {[key: string]: mixed},
  prevProps: ContainerProps,
  relay: RelayContext,
  relayEnvironment: IEnvironment | ClassicEnvironment,
  relayProp: RelayProp,
  relayVariables: Variables,
  resolver: FragmentSpecResolver,
};

/**
 * Composes a React component class, returning a new class that intercepts
 * props, resolving them with the provided fragments and subscribing for
 * updates.
 */
function createContainerWithFragments<
  Props: {},
  TComponent: React.ComponentType<Props>,
>(
  Component: TComponent,
  fragments: FragmentMap,
): React.ComponentType<
  $RelayProps<React.ElementConfig<TComponent>, RelayProp>,
> {
  const containerName = getContainerName(Component);

  class Container extends React.Component<ContainerProps, ContainerState> {
    static displayName = containerName;
    static contextTypes = {
      relay: RelayPropTypes.Relay,
    };

    constructor(props, context) {
      super(props, context);
      const relay = assertRelayContext(context.relay);
      const {createFragmentSpecResolver} = relay.environment.unstable_internal;
      // Do not provide a subscription/callback here.
      // It is possible for this render to be interrupted or aborted,
      // In which case the subscription would cause a leak.
      // We will add the subscription in componentDidMount().
      const resolver = createFragmentSpecResolver(
        relay,
        containerName,
        fragments,
        props,
      );
      this.state = {
        data: resolver.resolve(),
        relay,
        relayEnvironment: context.relay.environment,
        prevProps: this.props,
        relayVariables: context.relay.variables,
        relayProp: {
          isLoading: resolver.isLoading(),
          environment: relay.environment,
        },
        resolver,
      };
    }

    /**
     * When new props are received, read data for the new props and subscribe
     * for updates. Props may be the same in which case previous data and
     * subscriptions can be reused.
     */
    static getDerivedStateFromProps(
      nextProps: ContainerProps,
      prevState: ContainerState,
    ): $Shape<ContainerState> | null {
      // Any props change could impact the query, so we mirror props in state.
      // This is an unusual pattern, but necessary for this container usecase.
      const {prevProps, relay} = prevState;

      const {
        createFragmentSpecResolver,
        getDataIDsFromObject,
      } = relay.environment.unstable_internal;
      const prevIDs = getDataIDsFromObject(fragments, prevProps);
      const nextIDs = getDataIDsFromObject(fragments, nextProps);

      let resolver: FragmentSpecResolver = prevState.resolver;

      // If the environment has changed or props point to new records then
      // previously fetched data and any pending fetches no longer apply:
      // - Existing references are on the old environment.
      // - Existing references are based on old variables.
      // - Pending fetches are for the previous records.
      if (
        prevState.relayEnvironment !== relay.environment ||
        prevState.relayVariables !== relay.variables ||
        !areEqual(prevIDs, nextIDs)
      ) {
        // Do not provide a subscription/callback here.
        // It is possible for this render to be interrupted or aborted,
        // In which case the subscription would cause a leak.
        // We will add the subscription in componentDidUpdate().
        resolver = createFragmentSpecResolver(
          relay,
          containerName,
          fragments,
          nextProps,
        );

        return {
          data: resolver.resolve(),
          relayEnvironment: relay.environment,
          prevProps: nextProps,
          relayVariables: relay.variables,
          relayProp: {
            isLoading: resolver.isLoading(),
            environment: relay.environment,
          },
          resolver,
        };
      } else {
        resolver.setProps(nextProps);

        const data = resolver.resolve();
        if (data !== prevState.data) {
          return {
            data,
            relayEnvironment: relay.environment,
            prevProps: nextProps,
            relayVariables: relay.variables,
            relayProp: {
              isLoading: resolver.isLoading(),
              environment: relay.environment,
            },
          };
        }
      }

      return null;
    }

    componentDidMount() {
      this._subscribeToNewResolver();
    }

    componentDidUpdate(prevProps: ContainerProps, prevState: ContainerState) {
      if (this.state.resolver !== prevState.resolver) {
        prevState.resolver.dispose();

        this._subscribeToNewResolver();
      }
    }

    componentWillUnmount() {
      this.state.resolver.dispose();
    }

    shouldComponentUpdate(nextProps, nextState): boolean {
      // Short-circuit if any Relay-related data has changed
      if (nextState.data !== this.state.data) {
        return true;
      }
      // Otherwise, for convenience short-circuit if all non-Relay props
      // are scalar and equal
      const keys = Object.keys(nextProps);
      for (let ii = 0; ii < keys.length; ii++) {
        const key = keys[ii];
        if (key === 'relay') {
          if (
            nextState.relayEnvironment !== this.state.relayEnvironment ||
            nextState.relayVariables !== this.state.relayVariables
          ) {
            return true;
          }
        } else {
          if (
            !fragments.hasOwnProperty(key) &&
            !isScalarAndEqual(nextProps[key], this.props[key])
          ) {
            return true;
          }
        }
      }
      return false;
    }

    /**
     * Render new data for the existing props/context.
     */
    _handleFragmentDataUpdate = () => {
      const profiler = RelayProfiler.profile(
        'ReactRelayFragmentContainer.handleFragmentDataUpdate',
      );
      const resolverFromThisUpdate = this.state.resolver;
      this.setState(updatedState => {
        // If this event belongs to the current data source, update.
        // Otherwise we should ignore it.
        if (resolverFromThisUpdate === updatedState.resolver) {
          return {
            data: updatedState.resolver.resolve(),
            relayProp: {
              isLoading: updatedState.resolver.isLoading(),
              environment: updatedState.relayProp.environment,
            },
          };
        }

        return null;
      }, profiler.stop);
    };

    _subscribeToNewResolver() {
      const {data, resolver} = this.state;

      // Event listeners are only safe to add during the commit phase,
      // So they won't leak if render is interrupted or errors.
      resolver.setCallback(this._handleFragmentDataUpdate);

      // External values could change between render and commit.
      // Check for this case, even though it requires an extra store read.
      const maybeNewData = resolver.resolve();
      if (data !== maybeNewData) {
        this.setState({data: maybeNewData});
      }
    }

    render() {
      const {componentRef, ...props} = this.props;
      return React.createElement(Component, {
        ...props,
        ...this.state.data,
        ref: componentRef,
        relay: this.state.relayProp,
      });
    }
  }
  profileContainer(Container, 'ReactRelayFragmentContainer');

  return Container;
}

/**
 * Wrap the basic `createContainer()` function with logic to adapt to the
 * `context.relay.environment` in which it is rendered. Specifically, the
 * extraction of the environment-specific version of fragments in the
 * `fragmentSpec` is memoized once per environment, rather than once per
 * instance of the container constructed/rendered.
 */
function createContainer<Props: {}, TComponent: React.ComponentType<Props>>(
  Component: TComponent,
  fragmentSpec: GraphQLTaggedNode | GeneratedNodeMap,
): React.ComponentType<
  $RelayProps<React.ElementConfig<TComponent>, RelayProp>,
> {
  return buildReactRelayContainer(
    Component,
    fragmentSpec,
    createContainerWithFragments,
    /* provides child context */ false,
  );
}

module.exports = {createContainer, createContainerWithFragments};
<commit_msg>Fix the Relay bug when the components got teared apart because of inconsistency of store data between rendering and committing<commit_after>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const React = require('React');
const RelayPropTypes = require('../classic/container/RelayPropTypes');

const areEqual = require('areEqual');
const buildReactRelayContainer = require('./buildReactRelayContainer');

const {assertRelayContext} = require('../classic/environment/RelayContext');
const {profileContainer} = require('./ReactRelayContainerProfiler');
const {getContainerName} = require('./ReactRelayContainerUtils');
const {RelayProfiler, isScalarAndEqual} = require('RelayRuntime');

import type {FragmentSpecResolver} from '../classic/environment/RelayCombinedEnvironmentTypes';
import type {RelayEnvironmentInterface as ClassicEnvironment} from '../classic/store/RelayEnvironment';
import type {$RelayProps, GeneratedNodeMap, RelayProp} from './ReactRelayTypes';
import type {
  FragmentMap,
  GraphQLTaggedNode,
  IEnvironment,
  RelayContext,
  Variables,
} from 'RelayRuntime';

type ContainerProps = $FlowFixMeProps;
type ContainerState = {
  data: {[key: string]: mixed},
  prevProps: ContainerProps,
  relay: RelayContext,
  relayEnvironment: IEnvironment | ClassicEnvironment,
  relayProp: RelayProp,
  relayVariables: Variables,
  resolver: FragmentSpecResolver,
};

/**
 * Composes a React component class, returning a new class that intercepts
 * props, resolving them with the provided fragments and subscribing for
 * updates.
 */
function createContainerWithFragments<
  Props: {},
  TComponent: React.ComponentType<Props>,
>(
  Component: TComponent,
  fragments: FragmentMap,
): React.ComponentType<
  $RelayProps<React.ElementConfig<TComponent>, RelayProp>,
> {
  const containerName = getContainerName(Component);

  class Container extends React.Component<ContainerProps, ContainerState> {
    static displayName = containerName;
    static contextTypes = {
      relay: RelayPropTypes.Relay,
    };

    constructor(props, context) {
      super(props, context);
      const relay = assertRelayContext(context.relay);
      const {createFragmentSpecResolver} = relay.environment.unstable_internal;
      // Do not provide a subscription/callback here.
      // It is possible for this render to be interrupted or aborted,
      // In which case the subscription would cause a leak.
      // We will add the subscription in componentDidMount().
      const resolver = createFragmentSpecResolver(
        relay,
        containerName,
        fragments,
        props,
      );
      this.state = {
        data: resolver.resolve(),
        relay,
        relayEnvironment: context.relay.environment,
        prevProps: this.props,
        relayVariables: context.relay.variables,
        relayProp: {
          isLoading: resolver.isLoading(),
          environment: relay.environment,
        },
        resolver,
      };
    }

    /**
     * When new props are received, read data for the new props and subscribe
     * for updates. Props may be the same in which case previous data and
     * subscriptions can be reused.
     */
    static getDerivedStateFromProps(
      nextProps: ContainerProps,
      prevState: ContainerState,
    ): $Shape<ContainerState> | null {
      // Any props change could impact the query, so we mirror props in state.
      // This is an unusual pattern, but necessary for this container usecase.
      const {prevProps, relay} = prevState;

      const {
        createFragmentSpecResolver,
        getDataIDsFromObject,
      } = relay.environment.unstable_internal;
      const prevIDs = getDataIDsFromObject(fragments, prevProps);
      const nextIDs = getDataIDsFromObject(fragments, nextProps);

      let resolver: FragmentSpecResolver = prevState.resolver;

      // If the environment has changed or props point to new records then
      // previously fetched data and any pending fetches no longer apply:
      // - Existing references are on the old environment.
      // - Existing references are based on old variables.
      // - Pending fetches are for the previous records.
      if (
        prevState.relayEnvironment !== relay.environment ||
        prevState.relayVariables !== relay.variables ||
        !areEqual(prevIDs, nextIDs)
      ) {
        // Do not provide a subscription/callback here.
        // It is possible for this render to be interrupted or aborted,
        // In which case the subscription would cause a leak.
        // We will add the subscription in componentDidUpdate().
        resolver = createFragmentSpecResolver(
          relay,
          containerName,
          fragments,
          nextProps,
        );

        return {
          data: resolver.resolve(),
          relayEnvironment: relay.environment,
          prevProps: nextProps,
          relayVariables: relay.variables,
          relayProp: {
            isLoading: resolver.isLoading(),
            environment: relay.environment,
          },
          resolver,
        };
      } else {
        resolver.setProps(nextProps);

        const data = resolver.resolve();
        if (data !== prevState.data) {
          return {
            data,
            relayEnvironment: relay.environment,
            prevProps: nextProps,
            relayVariables: relay.variables,
            relayProp: {
              isLoading: resolver.isLoading(),
              environment: relay.environment,
            },
          };
        }
      }

      return null;
    }

    componentDidMount() {
      this._subscribeToNewResolver();
      this._rerenderIfStoreHasChanged();
    }

    componentDidUpdate(prevProps: ContainerProps, prevState: ContainerState) {
      if (this.state.resolver !== prevState.resolver) {
        prevState.resolver.dispose();

        this._subscribeToNewResolver();
      }
      this._rerenderIfStoreHasChanged();
    }

    componentWillUnmount() {
      this.state.resolver.dispose();
    }

    shouldComponentUpdate(nextProps, nextState): boolean {
      // Short-circuit if any Relay-related data has changed
      if (nextState.data !== this.state.data) {
        return true;
      }
      // Otherwise, for convenience short-circuit if all non-Relay props
      // are scalar and equal
      const keys = Object.keys(nextProps);
      for (let ii = 0; ii < keys.length; ii++) {
        const key = keys[ii];
        if (key === 'relay') {
          if (
            nextState.relayEnvironment !== this.state.relayEnvironment ||
            nextState.relayVariables !== this.state.relayVariables
          ) {
            return true;
          }
        } else {
          if (
            !fragments.hasOwnProperty(key) &&
            !isScalarAndEqual(nextProps[key], this.props[key])
          ) {
            return true;
          }
        }
      }
      return false;
    }

    /**
     * Render new data for the existing props/context.
     */
    _handleFragmentDataUpdate = () => {
      const profiler = RelayProfiler.profile(
        'ReactRelayFragmentContainer.handleFragmentDataUpdate',
      );
      const resolverFromThisUpdate = this.state.resolver;
      this.setState(updatedState => {
        // If this event belongs to the current data source, update.
        // Otherwise we should ignore it.
        if (resolverFromThisUpdate === updatedState.resolver) {
          return {
            data: updatedState.resolver.resolve(),
            relayProp: {
              isLoading: updatedState.resolver.isLoading(),
              environment: updatedState.relayProp.environment,
            },
          };
        }

        return null;
      }, profiler.stop);
    };

    _rerenderIfStoreHasChanged() {
      const {data, resolver} = this.state;
      // External values could change between render and commit.
      // Check for this case, even though it requires an extra store read.
      const maybeNewData = resolver.resolve();
      if (data !== maybeNewData) {
        this.setState({data: maybeNewData});
      }
    }

    _subscribeToNewResolver() {
      const {resolver} = this.state;

      // Event listeners are only safe to add during the commit phase,
      // So they won't leak if render is interrupted or errors.
      resolver.setCallback(this._handleFragmentDataUpdate);
    }

    render() {
      const {componentRef, ...props} = this.props;
      return React.createElement(Component, {
        ...props,
        ...this.state.data,
        ref: componentRef,
        relay: this.state.relayProp,
      });
    }
  }
  profileContainer(Container, 'ReactRelayFragmentContainer');

  return Container;
}

/**
 * Wrap the basic `createContainer()` function with logic to adapt to the
 * `context.relay.environment` in which it is rendered. Specifically, the
 * extraction of the environment-specific version of fragments in the
 * `fragmentSpec` is memoized once per environment, rather than once per
 * instance of the container constructed/rendered.
 */
function createContainer<Props: {}, TComponent: React.ComponentType<Props>>(
  Component: TComponent,
  fragmentSpec: GraphQLTaggedNode | GeneratedNodeMap,
): React.ComponentType<
  $RelayProps<React.ElementConfig<TComponent>, RelayProp>,
> {
  return buildReactRelayContainer(
    Component,
    fragmentSpec,
    createContainerWithFragments,
    /* provides child context */ false,
  );
}

module.exports = {createContainer, createContainerWithFragments};
<|endoftext|>


<commit_before>
const { expect } = require('chai');
const BoundlessCache = require('../cache/boundless');
const ExpirationCache = require('../cache/expiration');
const RemovalCause = require('../utils/removal-cause');

const newCache = (listener) => {
	let Impl = ExpirationCache(BoundlessCache);
	return new Impl({
		maxWriteAge: () => 100,
		removalListener: listener
	});
};

const newReadCache = (listener) => {
	let Impl = ExpirationCache(BoundlessCache);
	return new Impl({
		maxNoReadAge: () => 10,
		removalListener: listener
	});
};

describe('ExpirationCache', function() {
	it('Can create', function() {
		expect(newCache()).to.not.be.null;
	});

	describe('With maxWriteAge', function() {
		it('Set value in cache', function() {
			const cache = newCache();
			cache.set('key', 'value');

			expect(cache.has('key')).to.equal(true);
			expect(cache.getIfPresent('key')).to.equal('value');
		});

		it('Set value in cache with options', function() {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: 10 });

			expect(cache.has('key')).to.equal(true);
			expect(cache.getIfPresent('key')).to.equal('value');
		});

		it('Set value in cache with 0 age', function() {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: 0 });

			expect(cache.has('key')).to.equal(false);
		});

		it('Get non-existent value in cache', function() {
			const cache = newCache();

			expect(cache.getIfPresent('key')).to.equal(null);
		});

		it('Delete works', function() {
			const cache = newCache();
			cache.set('key', 'value');

			cache.delete('key');
			expect(cache.getIfPresent('key')).to.equal(null);
		});

		it('Set value in cache with timeout', function(cb) {
			const cache = newCache();
			cache.set('key', 'value');

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 200);
		});

		it('Set value in cache with options and timeout', function(cb) {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: 50 });

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 51);
		});

		it('Set value in cache with options (string) and timeout', function(cb) {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: '50 ms' });

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 51);
		});

		it('Set evicts old keys', function(cb) {
			const cache = newCache();
			cache.set('key', 'value');

			setTimeout(() => {
				cache.set('key2', 'value');
				cache.__await();
				expect(cache.size).to.equal(1);
				cb();
			}, 1080);
		});
	});

	describe('With maxNoReadAge', function() {
		it('Set value in cache', function() {
			const cache = newReadCache();
			cache.set('key', 'value');

			expect(cache.has('key')).to.equal(true);
			expect(cache.getIfPresent('key')).to.equal('value');
		});

		it('Set value in cache with timeout', function(cb) {
			const cache = newReadCache();
			cache.set('key', 'value');

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 15);
		});

		it('Set and get value in cache with timeout', function(cb) {
			const cache = newReadCache();
			cache.set('key', 'value');

			// Trigger the max age read
			cache.get('key');

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 15);
		});
	});

	describe('Removal listeners', function() {
		it('Triggers on delete', function() {
			const listener = removalListener();
			const cache = newCache(listener);

			cache.set('one', 1234);
			expect(listener.removed).to.equal(null);

			cache.delete('one');
			expect(listener.removed).to.deep.equal({
				key: 'one',
				value: 1234,
				reason: RemovalCause.EXPLICIT
			});
		});

		it('Triggers on set', function() {
			const listener = removalListener();
			const cache = newCache(listener);

			cache.set('one', 1234);
			expect(listener.removed).to.equal(null);

			cache.set('one', 4321);
			expect(listener.removed).to.deep.equal({
				key: 'one',
				value: 1234,
				reason: RemovalCause.REPLACED
			});
		});

		it('Triggers on expiration', function(cb) {
			const listener = removalListener();
			const cache = newCache(listener);

			cache.set('one', 1234);
			expect(listener.removed).to.equal(null);

			setTimeout(() => {
				cache.set('one', 4321);
				expect(listener.removed).to.deep.equal({
					key: 'one',
					value: 1234,
					reason: RemovalCause.EXPIRED
				});
				cb();
			}, 200);
		});
	});
});

function removalListener() {
	let result = (key, value, reason) => {
		result.removed = { key, value, reason };
	};
	result.removed = null;
	return result;
}
<commit_msg>Wait 60 ms instead of 51 on expiration<commit_after>
const { expect } = require('chai');
const BoundlessCache = require('../cache/boundless');
const ExpirationCache = require('../cache/expiration');
const RemovalCause = require('../utils/removal-cause');

const newCache = (listener) => {
	let Impl = ExpirationCache(BoundlessCache);
	return new Impl({
		maxWriteAge: () => 100,
		removalListener: listener
	});
};

const newReadCache = (listener) => {
	let Impl = ExpirationCache(BoundlessCache);
	return new Impl({
		maxNoReadAge: () => 10,
		removalListener: listener
	});
};

describe('ExpirationCache', function() {
	it('Can create', function() {
		expect(newCache()).to.not.be.null;
	});

	describe('With maxWriteAge', function() {
		it('Set value in cache', function() {
			const cache = newCache();
			cache.set('key', 'value');

			expect(cache.has('key')).to.equal(true);
			expect(cache.getIfPresent('key')).to.equal('value');
		});

		it('Set value in cache with options', function() {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: 10 });

			expect(cache.has('key')).to.equal(true);
			expect(cache.getIfPresent('key')).to.equal('value');
		});

		it('Set value in cache with 0 age', function() {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: 0 });

			expect(cache.has('key')).to.equal(false);
		});

		it('Get non-existent value in cache', function() {
			const cache = newCache();

			expect(cache.getIfPresent('key')).to.equal(null);
		});

		it('Delete works', function() {
			const cache = newCache();
			cache.set('key', 'value');

			cache.delete('key');
			expect(cache.getIfPresent('key')).to.equal(null);
		});

		it('Set value in cache with timeout', function(cb) {
			const cache = newCache();
			cache.set('key', 'value');

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 200);
		});

		it('Set value in cache with options and timeout', function(cb) {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: 50 });

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 60);
		});

		it('Set value in cache with options (string) and timeout', function(cb) {
			const cache = newCache();
			cache.set('key', 'value', { maxAge: '50 ms' });

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 60);
		});

		it('Set evicts old keys', function(cb) {
			const cache = newCache();
			cache.set('key', 'value');

			setTimeout(() => {
				cache.set('key2', 'value');
				cache.__await();
				expect(cache.size).to.equal(1);
				cb();
			}, 1080);
		});
	});

	describe('With maxNoReadAge', function() {
		it('Set value in cache', function() {
			const cache = newReadCache();
			cache.set('key', 'value');

			expect(cache.has('key')).to.equal(true);
			expect(cache.getIfPresent('key')).to.equal('value');
		});

		it('Set value in cache with timeout', function(cb) {
			const cache = newReadCache();
			cache.set('key', 'value');

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 15);
		});

		it('Set and get value in cache with timeout', function(cb) {
			const cache = newReadCache();
			cache.set('key', 'value');

			// Trigger the max age read
			cache.get('key');

			setTimeout(() => {
				expect(cache.get('key')).to.be.null;
				cb();
			}, 15);
		});
	});

	describe('Removal listeners', function() {
		it('Triggers on delete', function() {
			const listener = removalListener();
			const cache = newCache(listener);

			cache.set('one', 1234);
			expect(listener.removed).to.equal(null);

			cache.delete('one');
			expect(listener.removed).to.deep.equal({
				key: 'one',
				value: 1234,
				reason: RemovalCause.EXPLICIT
			});
		});

		it('Triggers on set', function() {
			const listener = removalListener();
			const cache = newCache(listener);

			cache.set('one', 1234);
			expect(listener.removed).to.equal(null);

			cache.set('one', 4321);
			expect(listener.removed).to.deep.equal({
				key: 'one',
				value: 1234,
				reason: RemovalCause.REPLACED
			});
		});

		it('Triggers on expiration', function(cb) {
			const listener = removalListener();
			const cache = newCache(listener);

			cache.set('one', 1234);
			expect(listener.removed).to.equal(null);

			setTimeout(() => {
				cache.set('one', 4321);
				expect(listener.removed).to.deep.equal({
					key: 'one',
					value: 1234,
					reason: RemovalCause.EXPIRED
				});
				cb();
			}, 200);
		});
	});
});

function removalListener() {
	let result = (key, value, reason) => {
		result.removed = { key, value, reason };
	};
	result.removed = null;
	return result;
}
<|endoftext|>


<commit_before>/// <reference path="chutzpahRunner.js" />
/*globals phantom, chutzpah, window, jasmine*/

(function () {
    'use strict';

    phantom.injectJs('chutzpahRunner.js');

    function onInitialized() {
        console.log("!!_!! onInitialized Jasmine - v1");

        var _cachedWindowLoad = window.onload;

        function startJasmine() {

            console.log("!!_!! Starting Jasmine from window onload...");

            var jasmineEnv = jasmine.getEnv();
            var runner = jasmineEnv.currentRunner();

            // Check if runner hasn't been executed
            // If so, run it
            if (!runner.queue.running && runner.queue.index <= 0) {
                jasmineEnv.execute();
            }

        }

        window.onload = function () {
            if (_cachedWindowLoad) {
                _cachedWindowLoad();
            }

            if (window.chutzpah.usingModuleLoader) {
                console.log("!!_!! Test file is using module loader.");
                // Since we are using a module loader let the harness determine when its ready to run tests
                return;
            }

            startJasmine();
        };
    }

    function isTestingDone() {
        console.log("!!_!! isTestingDone");
        return window.chutzpah.isTestingFinished === true;
    }

    function isJamineLoaded() {
        console.log("!!_!! isJasmineLoaded");
        return window.jasmine;
    }

    function onJasmineLoaded() {

        console.log("!!_!! onJasmineLoaded");

        function log(obj) {
            console.log(JSON.stringify(obj));
        }

        function patchDdescribeIitSupport() {
            if (window.ddescribeIitSupport) {
                window.ddescribeIitSupport.patch(jasmine.getEnv());
            }
        }

        var activeTestCase = null,
            fileStartTime = null,
            testStartTime = null;
        window.chutzpah.isTestingFinished = false;
        window.chutzpah.testCases = [];

        function logCoverage() {
            if (window._Chutzpah_covobj_name && window[window._Chutzpah_covobj_name]) {
                log({ type: "CoverageObject", object: JSON.stringify(window[window._Chutzpah_covobj_name]) });
            }
        }

        function recordStackTrace(trace) {
            var stack = trace && trace.stack || null;
            if (stack) {
                stack = stack.split('\n').slice(1).join('\n');
            }
            return stack;
        }

        var ChutzpahJasmineReporter = function () {
            var self = this;

            self.reportRunnerStarting = function (runner) {
                // Must patch late since a HTML runner probably adds its on specFilter.
                patchDdescribeIitSupport();

                fileStartTime = new Date().getTime();

                // Testing began
                log({ type: "FileStart" });
            };

            self.reportRunnerResults = function (runner) {
                var res = jasmine.getEnv().currentRunner().results();
                var timetaken = new Date().getTime() - fileStartTime;
                logCoverage();
                log({ type: "FileDone", timetaken: timetaken, passed: res.passedCount, failed: res.failedCount });
                window.chutzpah.isTestingFinished = true;
            };

            self.reportSuiteResults = function (suite) { };

            self.reportSpecStarting = function (spec) {
                testStartTime = new Date().getTime();
                var suiteName = getFullSuiteName(spec.suite);
                var specName = spec.description;
                var newTestCase = { moduleName: suiteName, testName: specName, testResults: [] };
                window.chutzpah.testCases.push(newTestCase);
                activeTestCase = newTestCase;
                log({ type: "TestStart", testCase: activeTestCase });
            };

            self.reportSpecResults = function (spec) {
                var results = spec.results();
                if (results.skipped) {
                    return;
                }
                var timetaken = new Date().getTime() - testStartTime;
                activeTestCase.timetaken = timetaken;
                var resultItems = results.getItems();
                for (var i = 0; i < resultItems.length; i++) {
                    var result = resultItems[i];
                    var testResult = {};

                    // Check the existance of result.passed, don't call it!
                    if (result.passed) {
                        // result.passed() may return (true/false) or (1,0) but we want to only return boolean
                        testResult.passed = result.passed() ? true : false;
                        testResult.message = result.message;
                        testResult.stackTrace = recordStackTrace(result.trace);
                        activeTestCase.testResults.push(testResult);
                    } else {
                        // Not an ExpectationResult, probably a MessageResult. Treat as any other log message.
                        log({ type: 'Log', log: { message: result.toString() } });
                    }
                }

                // Log test case when done. This will get picked up by phantom and streamed to chutzpah.
                log({ type: "TestDone", testCase: activeTestCase });
            };

            self.log = function () {
                var console = jasmine.getGlobal().console;
                if (console && console.log) {
                    if (console.log.apply) {
                        console.log.apply(console, arguments);
                    } else {
                        console.log(arguments);
                    }
                }
            };

            self.specFilter = function (spec) {
                return true;
            };

            function getFullSuiteName(suite) {
                var description = suite.description;
                if (suite.parentSuite) {
                    description = getFullSuiteName(suite.parentSuite) + " " + description;
                }

                return description;
            }

            return self;
        };

        if (window.chutzpah.testMode) {
            jasmine.getEnv().addReporter(new ChutzpahJasmineReporter());
        }

        if (window.chutzpah.testMode === 'discovery') {
            // If discovery mode overwrite execute to not run the test
            jasmine.Block.prototype.execute = function (onComplete) {
                onComplete();
            };
        }
    }

    function onPageLoaded() {
        console.log("!!_!! onPageLoaded");
    }

    try {
        chutzpah.runner(onInitialized, onPageLoaded, isJamineLoaded, onJasmineLoaded, isTestingDone);
    } catch (e) {
        phantom.exit(2); // Unkown error
    }
}());
<commit_msg>Fix odd race condition<commit_after>/// <reference path="chutzpahRunner.js" />
/*globals phantom, chutzpah, window, jasmine*/

(function () {
    'use strict';

    phantom.injectJs('chutzpahRunner.js');

    function onInitialized() {
        console.log("!!_!! onInitialized Jasmine - v1");

        var _cachedWindowLoad = window.onload;

        function startJasmine() {

            if (!window.jasmine) {
                return;
            }

            console.log("!!_!! Starting Jasmine from window onload...");

            var jasmineEnv = window.jasmine.getEnv();
            var runner = jasmineEnv.currentRunner();

            // Check if runner hasn't been executed
            // If so, run it
            if (!runner.queue.running && runner.queue.index <= 0) {
                jasmineEnv.execute();
            }

        }

        window.onload = function () {
            if (_cachedWindowLoad) {
                _cachedWindowLoad();
            }

            if (window.chutzpah.usingModuleLoader) {
                console.log("!!_!! Test file is using module loader.");
                // Since we are using a module loader let the harness determine when its ready to run tests
                return;
            }

            startJasmine();
        };
    }

    function isTestingDone() {
        console.log("!!_!! isTestingDone");
        return window.chutzpah.isTestingFinished === true;
    }

    function isJamineLoaded() {
        console.log("!!_!! isJasmineLoaded");
        return window.jasmine;
    }

    function onJasmineLoaded() {

        console.log("!!_!! onJasmineLoaded");

        function log(obj) {
            console.log(JSON.stringify(obj));
        }

        function patchDdescribeIitSupport() {
            if (window.ddescribeIitSupport) {
                window.ddescribeIitSupport.patch(window.jasmine.getEnv());
            }
        }

        var activeTestCase = null,
            fileStartTime = null,
            testStartTime = null;
        window.chutzpah.isTestingFinished = false;
        window.chutzpah.testCases = [];

        function logCoverage() {
            if (window._Chutzpah_covobj_name && window[window._Chutzpah_covobj_name]) {
                log({ type: "CoverageObject", object: JSON.stringify(window[window._Chutzpah_covobj_name]) });
            }
        }

        function recordStackTrace(trace) {
            var stack = trace && trace.stack || null;
            if (stack) {
                stack = stack.split('\n').slice(1).join('\n');
            }
            return stack;
        }

        var ChutzpahJasmineReporter = function () {
            var self = this;

            self.reportRunnerStarting = function (runner) {
                // Must patch late since a HTML runner probably adds its on specFilter.
                patchDdescribeIitSupport();

                fileStartTime = new Date().getTime();

                // Testing began
                log({ type: "FileStart" });
            };

            self.reportRunnerResults = function (runner) {
                var res = window.jasmine.getEnv().currentRunner().results();
                var timetaken = new Date().getTime() - fileStartTime;
                logCoverage();
                log({ type: "FileDone", timetaken: timetaken, passed: res.passedCount, failed: res.failedCount });
                window.chutzpah.isTestingFinished = true;
            };

            self.reportSuiteResults = function (suite) { };

            self.reportSpecStarting = function (spec) {
                testStartTime = new Date().getTime();
                var suiteName = getFullSuiteName(spec.suite);
                var specName = spec.description;
                var newTestCase = { moduleName: suiteName, testName: specName, testResults: [] };
                window.chutzpah.testCases.push(newTestCase);
                activeTestCase = newTestCase;
                log({ type: "TestStart", testCase: activeTestCase });
            };

            self.reportSpecResults = function (spec) {
                var results = spec.results();
                if (results.skipped) {
                    return;
                }
                var timetaken = new Date().getTime() - testStartTime;
                activeTestCase.timetaken = timetaken;
                var resultItems = results.getItems();
                for (var i = 0; i < resultItems.length; i++) {
                    var result = resultItems[i];
                    var testResult = {};

                    // Check the existance of result.passed, don't call it!
                    if (result.passed) {
                        // result.passed() may return (true/false) or (1,0) but we want to only return boolean
                        testResult.passed = result.passed() ? true : false;
                        testResult.message = result.message;
                        testResult.stackTrace = recordStackTrace(result.trace);
                        activeTestCase.testResults.push(testResult);
                    } else {
                        // Not an ExpectationResult, probably a MessageResult. Treat as any other log message.
                        log({ type: 'Log', log: { message: result.toString() } });
                    }
                }

                // Log test case when done. This will get picked up by phantom and streamed to chutzpah.
                log({ type: "TestDone", testCase: activeTestCase });
            };

            self.log = function () {
                var console = window.jasmine.getGlobal().console;
                if (console && console.log) {
                    if (console.log.apply) {
                        console.log.apply(console, arguments);
                    } else {
                        console.log(arguments);
                    }
                }
            };

            self.specFilter = function (spec) {
                return true;
            };

            function getFullSuiteName(suite) {
                var description = suite.description;
                if (suite.parentSuite) {
                    description = getFullSuiteName(suite.parentSuite) + " " + description;
                }

                return description;
            }

            return self;
        };

        if (window.chutzpah.testMode) {
            window.jasmine.getEnv().addReporter(new ChutzpahJasmineReporter());
        }

        if (window.chutzpah.testMode === 'discovery') {
            // If discovery mode overwrite execute to not run the test
            window.jasmine.Block.prototype.execute = function (onComplete) {
                onComplete();
            };
        }
    }

    function onPageLoaded() {
        console.log("!!_!! onPageLoaded");
    }

    try {
        chutzpah.runner(onInitialized, onPageLoaded, isJamineLoaded, onJasmineLoaded, isTestingDone);
    } catch (e) {
        phantom.exit(2); // Unkown error
    }
}());
<|endoftext|>


<commit_before>FLOW.NavMapsView = Ember.View.extend({
  templateName: "navMaps/nav-maps",
  showDetailsBool: false,

  PlacemarkDetailsView:Ember.View.extend({

  }),

  init: function () {
    this._super();
    FLOW.placemarkControl.populate();
  },

  didInsertElement: function () {
    var map = new mxn.Mapstraction("flowMap", "google", true),
        latlon = new mxn.LatLonPoint(-0.703107, 36.765747);

    map.addControls({
        pan: true,
        zoom: 'small',
        map_type: true
    });

    map.setCenterAndZoom(latlon, 8);
    map.enableScrollWheelZoom();
    FLOW.placemarkControl.set('map', map);
  },

  // Returns a marker(pin on the map) to represent the placemarker
  createMarker: function (placemark) {
    // Create a marker
    var point = new mxn.LatLonPoint(placemark.get('latitude'), placemark.get('longitude')),
      marker = new mxn.Marker(point);

    marker.setIcon('flow15/images/maps/blueMarker.png');
    marker.placemark = placemark;
    // Add a click handler that handles what happens when marker is clicked

    placemark.toggleMarker = function (placemark) {
      var map = FLOW.placemarkControl.get('map');
      var point = new mxn.LatLonPoint(placemark.get('latitude'), placemark.get('longitude')),
        newMarker = new mxn.Marker(point);

      if (placemark.marker.iconUrl === 'flow15/images/maps/blueMarker.png') {
        newMarker.iconUrl = 'flow15/images/maps/redMarker.png';
      } else {
        newMarker.iconUrl = 'flow15/images/maps/blueMarker.png';
      }

      placemark.addMarkerClickHandler(newMarker, placemark);
      map.addMarker(newMarker);
      map.removeMarker(placemark.marker);
      newMarker.placemark = placemark;

      placemark.set('marker', newMarker);
    },

    placemark.manageClick = function (marker) {
      // Handle new click
      marker.placemark.toggleMarker(marker.placemark);

      // If there was a marker already selected deselect
      var oldSelected = FLOW.placemarkControl.get('selected');
      if (typeof oldSelected === 'undefined') {
        console.log('No old selection');
        FLOW.placemarkControl.set('selected', placemark);
      } else {
        if (this.marker === oldSelected.marker) {
          console.log('Clicking the same marker');
          FLOW.placemarkControl.set('selected', undefined);
        } else {
          console.log('Clicking new marker');
          oldSelected.toggleMarker(oldSelected);
          FLOW.placemarkControl.set('selected', placemark);
        }
      }

    };

    placemark.addMarkerClickHandler = function (marker, placemark) {
      var clickHandler = function (event_name, event_source, event_args) {
        event_source.placemark.manageClick(event_source.placemark.marker);
      };
      marker.click.addHandler(clickHandler);
    };

    placemark.addMarkerClickHandler(marker, placemark);
    // Attach the new marker to the placemarker object
    placemark.set('marker', marker);

    return marker;
  },

  putPlacemarksOnMap: function () {
    var map = FLOW.placemarkControl.get('map');
    FLOW.placemarkControl.get('content').forEach(function (placemark) {
      var marker = this.createMarker(placemark);
      map.addMarker(marker);
    }, this);
  },

  triggerPlacemarks: function () {
    if (FLOW.placemarkControl.content.get('isUpdating') === false) {this.putPlacemarksOnMap();}
  }.observes('FLOW.placemarkControl.content.isUpdating'),

  getDetails: function () {
    // console.log('getting details');
    var selected = FLOW.placemarkControl.get('selected');
    if (typeof selected !== 'undefined') {
      FLOW.placemarkDetailControl.populate(selected.id);
    } else {
      FLOW.placemarkDetailControl.populate(selected);
    }
  }.observes('FLOW.placemarkControl.selected'),

  showDetails: function () {
    // console.log('details loaded');
    var content = FLOW.placemarkDetailControl.get('content');
    if ((typeof content !== 'undefined') && (content !== null)) {
      if (content.get('isLoaded') === true) {
        this.set('showDetailsBool', true);
      }
    }
  }.observes('FLOW.placemarkDetailControl.content.isLoaded')

});<commit_msg>get image URL from placemarkDetails<commit_after>FLOW.NavMapsView = Ember.View.extend({
  templateName: "navMaps/nav-maps",
  showDetailsBool: false,

  PlacemarkDetailsView:Ember.View.extend({

  }),

  init: function () {
    this._super();
    FLOW.placemarkControl.populate();
  },

  didInsertElement: function () {
    var map = new mxn.Mapstraction("flowMap", "google", true),
        latlon = new mxn.LatLonPoint(-0.703107, 36.765747);

    map.addControls({
        pan: true,
        zoom: 'small',
        map_type: true
    });

    map.setCenterAndZoom(latlon, 8);
    map.enableScrollWheelZoom();
    FLOW.placemarkControl.set('map', map);
  },

  // Returns a marker(pin on the map) to represent the placemarker
  createMarker: function (placemark) {
    // Create a marker
    var point = new mxn.LatLonPoint(placemark.get('latitude'), placemark.get('longitude')),
      marker = new mxn.Marker(point);

    marker.setIcon('flow15/images/maps/blueMarker.png');
    marker.placemark = placemark;
    // Add a click handler that handles what happens when marker is clicked

    placemark.toggleMarker = function (placemark) {
      var map = FLOW.placemarkControl.get('map');
      var point = new mxn.LatLonPoint(placemark.get('latitude'), placemark.get('longitude')),
        newMarker = new mxn.Marker(point);

      if (placemark.marker.iconUrl === 'flow15/images/maps/blueMarker.png') {
        newMarker.iconUrl = 'flow15/images/maps/redMarker.png';
      } else {
        newMarker.iconUrl = 'flow15/images/maps/blueMarker.png';
      }

      placemark.addMarkerClickHandler(newMarker, placemark);
      map.addMarker(newMarker);
      map.removeMarker(placemark.marker);
      newMarker.placemark = placemark;

      placemark.set('marker', newMarker);
    },

    placemark.manageClick = function (marker) {
      // Handle new click
      marker.placemark.toggleMarker(marker.placemark);

      // If there was a marker already selected deselect
      var oldSelected = FLOW.placemarkControl.get('selected');
      if (typeof oldSelected === 'undefined') {
        console.log('No old selection');
        FLOW.placemarkControl.set('selected', placemark);
      } else {
        if (this.marker === oldSelected.marker) {
          console.log('Clicking the same marker');
          FLOW.placemarkControl.set('selected', undefined);
        } else {
          console.log('Clicking new marker');
          oldSelected.toggleMarker(oldSelected);
          FLOW.placemarkControl.set('selected', placemark);
        }
      }

    };

    placemark.addMarkerClickHandler = function (marker, placemark) {
      var clickHandler = function (event_name, event_source, event_args) {
        event_source.placemark.manageClick(event_source.placemark.marker);
      };
      marker.click.addHandler(clickHandler);
    };

    placemark.addMarkerClickHandler(marker, placemark);
    // Attach the new marker to the placemarker object
    placemark.set('marker', marker);

    return marker;
  },

  putPlacemarksOnMap: function () {
    var map = FLOW.placemarkControl.get('map');
    FLOW.placemarkControl.get('content').forEach(function (placemark) {
      var marker = this.createMarker(placemark);
      map.addMarker(marker);
    }, this);
  },

  triggerPlacemarks: function () {
    if (FLOW.placemarkControl.content.get('isUpdating') === false) {this.putPlacemarksOnMap();}
  }.observes('FLOW.placemarkControl.content.isUpdating'),

  getDetails: function () {
    // console.log('getting details');
    var selected = FLOW.placemarkControl.get('selected');
    if (typeof selected !== 'undefined') {
      FLOW.placemarkDetailControl.populate(selected.id);
    } else {
      FLOW.placemarkDetailControl.populate(selected);
    }
  }.observes('FLOW.placemarkControl.selected'),

  showDetails: function () {
    // console.log('details loaded');
    var content = FLOW.placemarkDetailControl.get('content');
    if ((typeof content !== 'undefined') && (content !== null)) {
      if (content.get('isLoaded') === true) {
        this.set('showDetailsBool', true);
      }

      var imageString=null;
      if (FLOW.placemarkDetailControl.content.get('isLoaded') === true) {
        this.set('showDetailsBool', true);

        var containsImage = FLOW.store.filter(FLOW.PlacemarkDetail,function(data){
        var stringVal=data.get('stringValue');
        if (stringVal.indexOf('wfpPhoto') != -1) {return true;} else {return false;}
        });

        imageObj = containsImage.get('firstObject');
        imageString = imageObj.get('stringValue');
        imageURL = 'http://flowdemo.s3.amazonaws.com/images/' + imageString.slice(imageString.indexOf('wfpPhoto'));

        console.log(imageURL);
        // over to you Daniel!
      }
    }
  }.observes('FLOW.placemarkDetailControl.content.isLoaded')

});<|endoftext|>


<commit_before>const isBrowser = require('is-browser')
let canvas = null
let ctx2d = null
const W = 250
const H = 800
const M = 10
const LINE_H = 16
const FONT_H = 11
const FONT = FONT_H + 'px Droid Sans Mono, Andale Mono, monospace'

function ms (time) {
  var f = Math.floor(time * 10) / 10
  var s = '' + f
  if (f % 1 === 0) s += '.0'
  if (f < 10) s = ' ' + s
  return s + ''
}

function pa3 (f) {
  if (f < 10) return '  ' + f
  if (f < 100) return ' ' + f
  return f
}

function createProfiler (ctx, renderer) {
  const gl = ctx.gl
  if (isBrowser && !canvas) {
    canvas = document.createElement('canvas')
    canvas.id = 'pex-renderer-profiler'
    canvas.width = W * 2
    canvas.height = H * 2
    document.body.appendChild(canvas)
    canvas.style.position = 'fixed'
    canvas.style.width = W + 'px'
    canvas.style.height = H + 'px'
    canvas.style.top = M + 'px'
    canvas.style.right = M + 'px'
    canvas.style.zIndex = 1000
    ctx2d = canvas.getContext('2d')
  }

  const profiler = {
    canvas: canvas,
    frame: 0,
    flush: true,
    measurements: {},
    commands: [],
    ctx2d: ctx2d,
    bufferCount: 0,
    totalBufferCount: 0,
    textureCount: 0,
    totalTextureCount: 0,
    programCount: 0,
    totalProgramCount: 0,
    framebufferCount: 0,
    totalFramebufferCount: 0,
    bindTextureCount: 0,
    useProgramCount: 0,
    setUniformCount: 0,
    trianglesCount: 0,
    drawElementsCount: 0,
    drawElementsInstancedCount: 0,
    drawArraysCount: 0,
    drawArraysInstancedCount: 0,
    linesCount: 0,
    time: function (label, gpu) {
      if (this.flush) gl.finish()
      if (this.flush) gl.flush()
      let m = this.measurements[label]
      if (!m) {
        m = this.measurements[label] = { begin: 0, end: 0, last: 0, total: 0, count: 0, avg: 0, max: 0 }
        if (gpu) {
          m.query = ctx.query()
        }
      }
      this.measurements[label].start = window.performance ? window.performance.now() : Date.now()
      if (m.query && m.query.result) m.gpu = m.query.result / 1000000
      if (m.query) ctx.beginQuery(m.query)
    },
    timeEnd: function (label) {
      if (this.flush) gl.finish()
      if (this.flush) gl.flush()
      const m = this.measurements[label]
      if (!m) {
        return
      }
      m.end = window.performance ? window.performance.now() : Date.now()
      m.last = m.end - m.start
      m.total += m.last
      m.max = Math.max(m.max, m.last)
      m.count++
      m.avg = (m.avg * 9 + m.last * 1) / 10
      if (m.query) ctx.endQuery(m.query)
    },
    add: function (command, label) {
      let callStack = null
      try {
        throw new Error('Call stack capture')
      } catch (e) {
        callStack = e.stack
      }
      this.commands.push({
        command: command,
        stack: callStack,
        label: label
      })
    },
    setFlush: function (state) {
      this.flush = state
    },
    summary: function () {
      var lines = []
      const frameMeasurement = this.measurements['Frame']
      if (frameMeasurement) {
        lines.push(`FPS: ${(1000 / frameMeasurement.avg).toFixed(3)}`)
      }
      const frameRAFMeasurement = this.measurements['FrameRAF']
      if (frameRAFMeasurement) {
        lines.push(`FPS (RAF): ${(1000 / frameRAFMeasurement.avg).toFixed(3)}`)
      }
      lines.push('------')
      lines = lines.concat(Object.keys(this.measurements).sort((a, b) => {
        return this.measurements[a].start - this.measurements[b].start
      }).map((label) => {
        const m = this.measurements[label]
        return `${label}: ${ms(m.avg)} ${m.gpu ? ' / ' + (Math.floor(m.gpu * 10) / 10).toFixed(1) : ''}`
      }))
      lines.push('------')
      lines.push(`Entities: ${pa3(renderer.entities.length)}`)
      lines.push(`Geometries: ${pa3(renderer.getComponents('Geometry').length)}`)
      lines.push(`Materials: ${pa3(renderer.getComponents('Material').length)}`)
      lines.push(`Skins: ${pa3(renderer.getComponents('Skin').length)}`)
      lines.push(`Animations: ${pa3(renderer.getComponents('Animation').length)}`)
      lines.push(`Morphs: ${pa3(renderer.getComponents('Morph').length)}`)
      lines.push(`Cameras: ${pa3(renderer.getComponents('Camera').length)}`)
      lines.push(`Reflection Probes: ${pa3(renderer.getComponents('ReflectionProbe').length)}`)
      lines.push(`Skyboxes: ${pa3(renderer.getComponents('Skybox').length)}`)
      lines.push(`Point Lights: ${pa3(renderer.getComponents('PointLight').length)}`)
      lines.push(`Directional Lights: ${pa3(renderer.getComponents('DirectionalLight').length)}`)
      lines.push(`Spot Lights: ${pa3(renderer.getComponents('SpotLight').length)}`)
      lines.push(`Area Lights: ${pa3(renderer.getComponents('AreaLight').length)}`)
      lines.push('------')
      lines.push(`Programs: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'program').length)}`)
      lines.push(`Passes: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'pass').length)}`)
      lines.push(`Pipelines: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'pipeline').length)}`)
      lines.push(`Textures 2D: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'texture' && r.target === ctx.gl.TEXTURE_2D).length)}`)
      lines.push(`Textures Cube: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'texture' && r.target === ctx.gl.TEXTURE_CUBE_MAP).length)}`)
      lines.push('------')
      lines.push(`Buffers: ${pa3(profiler.bufferCount)} / ${pa3(profiler.totalBufferCount)}`)
      lines.push(`Textures: ${pa3(profiler.textureCount)} / ${pa3(profiler.totalTextureCount)}`)
      lines.push(`Programs: ${pa3(profiler.programCount)} / ${pa3(profiler.totalProgramCount)}`)
      lines.push(`FBOs: ${pa3(profiler.framebufferCount)} / ${pa3(profiler.totalFramebufferCount)}`)
      lines.push('------')
      lines.push(`Lines: ${profiler.linesCount}`)
      lines.push(`Triangles: ${profiler.trianglesCount}`)
      lines.push(`Instanced Lines: ${profiler.instancedLinesCount}`)
      lines.push(`Instanced Triangles: ${profiler.instancedTrianglesCount}`)
      lines.push('------')
      lines.push(`Bind Texture: ${pa3(profiler.bindTextureCount)}`)
      lines.push(`Use Program: ${pa3(profiler.useProgramCount)}`)
      lines.push(`Set Uniform: ${pa3(profiler.setUniformCount)}`)
      lines.push(`Draw Elements : ${pa3(profiler.drawElementsCount)}`)
      lines.push(`Instanced Draw Elements: ${pa3(profiler.drawElementsInstancedCount)}`)
      lines.push(`Draw Arrays : ${pa3(profiler.drawArraysCount)}`)
      lines.push(`Instanced Draw Arrays: ${pa3(profiler.drawArraysInstancedCount)}`)
      lines.push('------')
      lines = lines.concat(this.commands.map((cmd) => {
        // const cpu = cmd.command.stats.cpuTime / cmd.command.stats.count
        // const gpu = cmd.command.stats.gpuTime / cmd.command.stats.count
        // if (cmd.command.stats.count >= 30) {
          // cmd.command.stats.gpuTime = 0
          // cmd.command.stats.cpuTime = 0
          // cmd.command.stats.count = 0
        // }
        // return `${cmd.label}: ${ms(cpu)} ${ms(gpu)}`
        return `${cmd.label}: N/A`
      }))
      return lines
    },
    setEnabled: function (state) {
      if (isBrowser) {
        canvas.style.display = state ? 'block' : 'none'
      }
    },
    startFrame: function () {
      this.timeEnd('FrameRAF')
      this.time('FrameRAF')
      this.time('Frame')
      resetFrameStats()
    },
    endFrame: function () {
      this.timeEnd('Frame')
      draw()
    }
  }

  function draw () {
    profiler.frame++
    if (!ctx2d) {
      if (profiler.frame % 30 === 0) {
        console.log('profiler', profiler.summary())
      }
      return
    }
    const lines = profiler.summary()
    ctx2d.save()
    ctx2d.scale(2, 2)
    ctx2d.fillStyle = 'rgba(0, 0, 0, 0.5)'
    ctx2d.clearRect(0, 0, canvas.width, canvas.height)
    ctx2d.fillRect(0, 0, canvas.width, canvas.height)
    ctx2d.font = FONT
    ctx2d.fillStyle = '#FFF'
    lines.forEach((line, index) => {
      const w = ctx2d.measureText(line).width
      ctx2d.fillText(line, W - M - w, M + FONT_H + LINE_H * index)
    })
    ctx2d.restore()
  }

  // function wrapRes (fn, counter) {
    // const ctxFn = ctx[fn]
    // ctx[fn] = function () {
      // profiler[counter]++
      // return ctxFn.apply(this, arguments)
    // }
  // }

  // TODO:
  // pipelines, passes, etc
  // wrapRes('vertexBuffer', 'bufferCount')
  // wrapRes('elementsBuffer', 'elementsCount')
  // wrapRes('texture2D', 'text')
  // wrapRes('cube', 'totalTextureCubeCount')
  // wrapRes('framebuffer', 'totalFramebufferCount')

  function wrapGLCall (fn, callback) {
    const glFn = gl[fn]
    gl[fn] = function () {
      callback(arguments)
      return glFn.apply(this, arguments)
    }
  }

  // TODO
  wrapGLCall('createBuffer', () => {
    profiler.bufferCount++
    profiler.totalBufferCount++
  })
  wrapGLCall('deleteBuffer', () => {
    profiler.bufferCount--
  })

  wrapGLCall('createProgram', () => {
    profiler.programCount++
    profiler.totalProgramCount++
  })
  wrapGLCall('deleteProgram', () => {
    profiler.programCount--
  })

  wrapGLCall('createTexture', () => {
    profiler.textureCount++
    profiler.totalTextureCount++
  })
  wrapGLCall('deleteTexture', () => {
    profiler.textureCount--
  })

  wrapGLCall('createFramebuffer', () => {
    profiler.framebufferCount++
    profiler.totalFramebufferCount++
  })
  wrapGLCall('deleteFramebuffer', () => {
    profiler.framebufferCount--
  })

  wrapGLCall('bindTexture', () => profiler.bindTextureCount++)
  wrapGLCall('useProgram', () => profiler.useProgramCount++)

  wrapGLCall('drawElements', (args) => {
    const mode = args[0]
    const count = args[1]
    if (mode === gl.LINES) profiler.linesCount += count
    if (mode === gl.TRIANGLES) profiler.trianglesCount += count
    profiler.drawElementsCount++
  })
  wrapGLCall('drawArrays', (args) => {
    const mode = args[0]
    const count = args[2]
    if (mode === gl.LINES) profiler.linesCount += count
    if (mode === gl.TRIANGLES) profiler.trianglesCount += count
    profiler.drawArraysCount++
  })

  for (let prop in gl) {
    if (prop.indexOf('uniform') === 0) {
      wrapGLCall(prop, () => profiler.setUniformCount++)
    }
  }

  function wrapGLExtCall (ext, fn, callback) {
    if (!ext) {
      console.log('profiler', `Ext ${ext} it not available`)
      return
    }
    const extFn = ext[fn]
    ext[fn] = function () {
      callback(arguments)
      return extFn.apply(ext, arguments)
    }
  }

  // TODO: what about webgl2?
  wrapGLExtCall(gl.getExtension('ANGLE_instanced_arrays'), 'drawElementsInstancedANGLE', (args) => {
    const mode = args[0]
    const count = args[1]
    const primcount = args[4]
    if (mode === gl.LINES) profiler.instancedLinesCount += count * primcount // assuming divisor 1
    if (mode === gl.TRIANGLES) profiler.instancedTrianglesCount += count * primcount // assuming divisor 1
    profiler.drawElementsInstancedCount++
  })
  wrapGLExtCall(gl.getExtension('ANGLE_instanced_arrays'), 'drawArraysInstancedANGLE', (args) => {
    const mode = args[0]
    const count = args[2]
    const primcount = args[3]
    if (mode === gl.LINES) profiler['instancedLinesCount'] += count * primcount // assuming divisor 1
    if (mode === gl.TRIANGLES) profiler['instancedTrianglesCount'] += count * primcount // assuming divisor 1
    profiler.drawArraysInstancedCount++
  })

  function resetFrameStats () {
    profiler.bindTextureCount = 0
    profiler.useProgramCount = 0
    profiler.setUniformCount = 0
    profiler.linesCount = 0
    profiler.trianglesCount = 0
    profiler.instancedLinesCount = 0
    profiler.instancedTrianglesCount = 0
    profiler.drawElementsCount = 0
    profiler.drawElementsInstancedCount = 0
    profiler.drawArraysCount = 0
    profiler.drawArraysInstancedCount = 0
  }

  return profiler
}

module.exports = createProfiler
<commit_msg>Add VRAM counting to profiler<commit_after>const isBrowser = require('is-browser')
let canvas = null
let ctx2d = null
const W = 250
const H = 800
const M = 10
const LINE_H = 16
const FONT_H = 11
const FONT = FONT_H + 'px Droid Sans Mono, Andale Mono, monospace'

function ms (time) {
  var f = Math.floor(time * 10) / 10
  var s = '' + f
  if (f % 1 === 0) s += '.0'
  if (f < 10) s = ' ' + s
  return s + ''
}

function pa3 (f) {
  if (f < 10) return '  ' + f
  if (f < 100) return ' ' + f
  return f
}

function createProfiler (ctx, renderer) {
  const gl = ctx.gl
  if (isBrowser && !canvas) {
    canvas = document.createElement('canvas')
    canvas.id = 'pex-renderer-profiler'
    canvas.width = W * 2
    canvas.height = H * 2
    document.body.appendChild(canvas)
    canvas.style.position = 'fixed'
    canvas.style.width = W + 'px'
    canvas.style.height = H + 'px'
    canvas.style.top = M + 'px'
    canvas.style.right = M + 'px'
    canvas.style.zIndex = 1000
    ctx2d = canvas.getContext('2d')
  }

  const profiler = {
    canvas: canvas,
    frame: 0,
    flush: true,
    measurements: {},
    commands: [],
    ctx2d: ctx2d,
    bufferCount: 0,
    totalBufferCount: 0,
    textureCount: 0,
    totalTextureCount: 0,
    programCount: 0,
    totalProgramCount: 0,
    framebufferCount: 0,
    totalFramebufferCount: 0,
    bindTextureCount: 0,
    useProgramCount: 0,
    setUniformCount: 0,
    trianglesCount: 0,
    drawElementsCount: 0,
    drawElementsInstancedCount: 0,
    drawArraysCount: 0,
    drawArraysInstancedCount: 0,
    linesCount: 0,
    time: function (label, gpu) {
      if (this.flush) gl.finish()
      if (this.flush) gl.flush()
      let m = this.measurements[label]
      if (!m) {
        m = this.measurements[label] = { begin: 0, end: 0, last: 0, total: 0, count: 0, avg: 0, max: 0 }
        if (gpu) {
          m.query = ctx.query()
        }
      }
      this.measurements[label].start = window.performance ? window.performance.now() : Date.now()
      if (m.query && m.query.result) m.gpu = m.query.result / 1000000
      if (m.query) ctx.beginQuery(m.query)
    },
    timeEnd: function (label) {
      if (this.flush) gl.finish()
      if (this.flush) gl.flush()
      const m = this.measurements[label]
      if (!m) {
        return
      }
      m.end = window.performance ? window.performance.now() : Date.now()
      m.last = m.end - m.start
      m.total += m.last
      m.max = Math.max(m.max, m.last)
      m.count++
      m.avg = (m.avg * 9 + m.last * 1) / 10
      if (m.query) ctx.endQuery(m.query)
    },
    add: function (command, label) {
      let callStack = null
      try {
        throw new Error('Call stack capture')
      } catch (e) {
        callStack = e.stack
      }
      this.commands.push({
        command: command,
        stack: callStack,
        label: label
      })
    },
    setFlush: function (state) {
      this.flush = state
    },
    summary: function () {
      var lines = []
      const frameMeasurement = this.measurements['Frame']
      if (frameMeasurement) {
        lines.push(`FPS: ${(1000 / frameMeasurement.avg).toFixed(3)}`)
      }
      const frameRAFMeasurement = this.measurements['FrameRAF']
      if (frameRAFMeasurement) {
        lines.push(`FPS (RAF): ${(1000 / frameRAFMeasurement.avg).toFixed(3)}`)
      }
      lines.push('------')
      lines = lines.concat(Object.keys(this.measurements).sort((a, b) => {
        return this.measurements[a].start - this.measurements[b].start
      }).map((label) => {
        const m = this.measurements[label]
        return `${label}: ${ms(m.avg)} ${m.gpu ? ' / ' + (Math.floor(m.gpu * 10) / 10).toFixed(1) : ''}`
      }))
      var ctx = renderer._ctx
      var textures = ctx.resources.filter((r) => r.class === 'texture')
      var textureVRAM = 0
      textures.forEach((texture) => {
        var bits = 8
        var channels = 4
        if (texture.pixelFormat === ctx.PixelFormat.RGBA32F) {
          bits = 32
        }
        if (texture.pixelFormat === ctx.PixelFormat.RGBA16F) {
          bits = 16
        }
        if (texture.pixelFormat === ctx.PixelFormat.Depth) {
          bits = 24 // estimate
        }
        var bpp = bits / 8 * channels
        if (texture.target === ctx.gl.TEXTURE_2D) {
          textureVRAM += texture.width * texture.height * bpp
        } else if (texture.target === ctx.gl.TEXTURE_CUBE_MAP) {
          textureVRAM += texture.width * texture.height * bpp * 6
        }
      })
      lines.push('------')
      lines.push(`Entities: ${pa3(renderer.entities.length)}`)
      lines.push(`Geometries: ${pa3(renderer.getComponents('Geometry').length)}`)
      lines.push(`Materials: ${pa3(renderer.getComponents('Material').length)}`)
      lines.push(`Skins: ${pa3(renderer.getComponents('Skin').length)}`)
      lines.push(`Animations: ${pa3(renderer.getComponents('Animation').length)}`)
      lines.push(`Morphs: ${pa3(renderer.getComponents('Morph').length)}`)
      lines.push(`Cameras: ${pa3(renderer.getComponents('Camera').length)}`)
      lines.push(`Reflection Probes: ${pa3(renderer.getComponents('ReflectionProbe').length)}`)
      lines.push(`Skyboxes: ${pa3(renderer.getComponents('Skybox').length)}`)
      lines.push(`Point Lights: ${pa3(renderer.getComponents('PointLight').length)}`)
      lines.push(`Directional Lights: ${pa3(renderer.getComponents('DirectionalLight').length)}`)
      lines.push(`Spot Lights: ${pa3(renderer.getComponents('SpotLight').length)}`)
      lines.push(`Area Lights: ${pa3(renderer.getComponents('AreaLight').length)}`)
      lines.push('------')
      lines.push(`Programs: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'program').length)}`)
      lines.push(`Passes: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'pass').length)}`)
      lines.push(`Pipelines: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'pipeline').length)}`)
      lines.push(`Textures 2D: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'texture' && r.target === ctx.gl.TEXTURE_2D).length)}`)
      lines.push(`Textures Cube: ${pa3(renderer._ctx.resources.filter((r) => r.class === 'texture' && r.target === ctx.gl.TEXTURE_CUBE_MAP).length)}`)
      lines.push(`Texture VRAM: ${(textureVRAM / (1024 * 1024)).toFixed(0)}MB`)
      lines.push('------')
      lines.push(`Buffers: ${pa3(profiler.bufferCount)} / ${pa3(profiler.totalBufferCount)}`)
      lines.push(`Textures: ${pa3(profiler.textureCount)} / ${pa3(profiler.totalTextureCount)}`)
      lines.push(`Programs: ${pa3(profiler.programCount)} / ${pa3(profiler.totalProgramCount)}`)
      lines.push(`FBOs: ${pa3(profiler.framebufferCount)} / ${pa3(profiler.totalFramebufferCount)}`)
      lines.push('------')
      lines.push(`Lines: ${profiler.linesCount}`)
      lines.push(`Triangles: ${profiler.trianglesCount}`)
      lines.push(`Instanced Lines: ${profiler.instancedLinesCount}`)
      lines.push(`Instanced Triangles: ${profiler.instancedTrianglesCount}`)
      lines.push('------')
      lines.push(`Bind Texture: ${pa3(profiler.bindTextureCount)}`)
      lines.push(`Use Program: ${pa3(profiler.useProgramCount)}`)
      lines.push(`Set Uniform: ${pa3(profiler.setUniformCount)}`)
      lines.push(`Draw Elements : ${pa3(profiler.drawElementsCount)}`)
      lines.push(`Instanced Draw Elements: ${pa3(profiler.drawElementsInstancedCount)}`)
      lines.push(`Draw Arrays : ${pa3(profiler.drawArraysCount)}`)
      lines.push(`Instanced Draw Arrays: ${pa3(profiler.drawArraysInstancedCount)}`)
      lines.push('------')
      lines = lines.concat(this.commands.map((cmd) => {
        // const cpu = cmd.command.stats.cpuTime / cmd.command.stats.count
        // const gpu = cmd.command.stats.gpuTime / cmd.command.stats.count
        // if (cmd.command.stats.count >= 30) {
          // cmd.command.stats.gpuTime = 0
          // cmd.command.stats.cpuTime = 0
          // cmd.command.stats.count = 0
        // }
        // return `${cmd.label}: ${ms(cpu)} ${ms(gpu)}`
        return `${cmd.label}: N/A`
      }))
      return lines
    },
    setEnabled: function (state) {
      if (isBrowser) {
        canvas.style.display = state ? 'block' : 'none'
      }
    },
    startFrame: function () {
      this.timeEnd('FrameRAF')
      this.time('FrameRAF')
      this.time('Frame')
      resetFrameStats()
    },
    endFrame: function () {
      this.timeEnd('Frame')
      draw()
    }
  }

  function draw () {
    profiler.frame++
    if (!ctx2d) {
      if (profiler.frame % 30 === 0) {
        console.log('profiler', profiler.summary())
      }
      return
    }
    const lines = profiler.summary()
    ctx2d.save()
    ctx2d.scale(2, 2)
    ctx2d.fillStyle = 'rgba(0, 0, 0, 0.5)'
    ctx2d.clearRect(0, 0, canvas.width, canvas.height)
    ctx2d.fillRect(0, 0, canvas.width, canvas.height)
    ctx2d.font = FONT
    ctx2d.fillStyle = '#FFF'
    lines.forEach((line, index) => {
      const w = ctx2d.measureText(line).width
      ctx2d.fillText(line, W - M - w, M + FONT_H + LINE_H * index)
    })
    ctx2d.restore()
  }

  // function wrapRes (fn, counter) {
    // const ctxFn = ctx[fn]
    // ctx[fn] = function () {
      // profiler[counter]++
      // return ctxFn.apply(this, arguments)
    // }
  // }

  // TODO:
  // pipelines, passes, etc
  // wrapRes('vertexBuffer', 'bufferCount')
  // wrapRes('elementsBuffer', 'elementsCount')
  // wrapRes('texture2D', 'text')
  // wrapRes('cube', 'totalTextureCubeCount')
  // wrapRes('framebuffer', 'totalFramebufferCount')

  function wrapGLCall (fn, callback) {
    const glFn = gl[fn]
    gl[fn] = function () {
      callback(arguments)
      return glFn.apply(this, arguments)
    }
  }

  // TODO
  wrapGLCall('createBuffer', () => {
    profiler.bufferCount++
    profiler.totalBufferCount++
  })
  wrapGLCall('deleteBuffer', () => {
    profiler.bufferCount--
  })

  wrapGLCall('createProgram', () => {
    profiler.programCount++
    profiler.totalProgramCount++
  })
  wrapGLCall('deleteProgram', () => {
    profiler.programCount--
  })

  wrapGLCall('createTexture', () => {
    profiler.textureCount++
    profiler.totalTextureCount++
  })
  wrapGLCall('deleteTexture', () => {
    profiler.textureCount--
  })

  wrapGLCall('createFramebuffer', () => {
    profiler.framebufferCount++
    profiler.totalFramebufferCount++
  })
  wrapGLCall('deleteFramebuffer', () => {
    profiler.framebufferCount--
  })

  wrapGLCall('bindTexture', () => profiler.bindTextureCount++)
  wrapGLCall('useProgram', () => profiler.useProgramCount++)

  wrapGLCall('drawElements', (args) => {
    const mode = args[0]
    const count = args[1]
    if (mode === gl.LINES) profiler.linesCount += count
    if (mode === gl.TRIANGLES) profiler.trianglesCount += count
    profiler.drawElementsCount++
  })
  wrapGLCall('drawArrays', (args) => {
    const mode = args[0]
    const count = args[2]
    if (mode === gl.LINES) profiler.linesCount += count
    if (mode === gl.TRIANGLES) profiler.trianglesCount += count
    profiler.drawArraysCount++
  })

  for (let prop in gl) {
    if (prop.indexOf('uniform') === 0) {
      wrapGLCall(prop, () => profiler.setUniformCount++)
    }
  }

  function wrapGLExtCall (ext, fn, callback) {
    if (!ext) {
      console.log('profiler', `Ext ${ext} it not available`)
      return
    }
    const extFn = ext[fn]
    ext[fn] = function () {
      callback(arguments)
      return extFn.apply(ext, arguments)
    }
  }

  // TODO: what about webgl2?
  wrapGLExtCall(gl.getExtension('ANGLE_instanced_arrays'), 'drawElementsInstancedANGLE', (args) => {
    const mode = args[0]
    const count = args[1]
    const primcount = args[4]
    if (mode === gl.LINES) profiler.instancedLinesCount += count * primcount // assuming divisor 1
    if (mode === gl.TRIANGLES) profiler.instancedTrianglesCount += count * primcount // assuming divisor 1
    profiler.drawElementsInstancedCount++
  })
  wrapGLExtCall(gl.getExtension('ANGLE_instanced_arrays'), 'drawArraysInstancedANGLE', (args) => {
    const mode = args[0]
    const count = args[2]
    const primcount = args[3]
    if (mode === gl.LINES) profiler['instancedLinesCount'] += count * primcount // assuming divisor 1
    if (mode === gl.TRIANGLES) profiler['instancedTrianglesCount'] += count * primcount // assuming divisor 1
    profiler.drawArraysInstancedCount++
  })

  function resetFrameStats () {
    profiler.bindTextureCount = 0
    profiler.useProgramCount = 0
    profiler.setUniformCount = 0
    profiler.linesCount = 0
    profiler.trianglesCount = 0
    profiler.instancedLinesCount = 0
    profiler.instancedTrianglesCount = 0
    profiler.drawElementsCount = 0
    profiler.drawElementsInstancedCount = 0
    profiler.drawArraysCount = 0
    profiler.drawArraysInstancedCount = 0
  }

  return profiler
}

module.exports = createProfiler
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

'use strict';

const getOutputForFixture = require('./getOutputForFixture');
const invariant = require('invariant');
const mapObject = require('mapObject');
const parseGraphQLText = require('./parseGraphQLText');

import type {GraphQLSchema} from 'graphql';
import type {RelayCompilerTransforms, IRTransform} from 'relay-compiler';
import type {GeneratedNode} from 'relay-runtime';

/* global expect,it */

/**
 * Extend Jest with a custom snapshot serializer to provide additional context
 * and reduce the amount of escaping that occurs.
 */
const FIXTURE_TAG = Symbol.for('FIXTURE_TAG');
expect.addSnapshotSerializer({
  print(value) {
    return Object.keys(value)
      .map(key => `~~~~~~~~~~ ${key.toUpperCase()} ~~~~~~~~~~\n${value[key]}`)
      .join('\n');
  },
  test(value) {
    return value && value[FIXTURE_TAG] === true;
  },
});

const matchers = {
  toBeDeeplyFrozen(actual) {
    function check(value) {
      expect(Object.isFrozen(value)).toBe(true);
      if (Array.isArray(value)) {
        value.forEach(item => check(item));
      } else if (typeof value === 'object' && value !== null) {
        for (const key in value) {
          check(value[key]);
        }
      }
    }
    check(actual);
    return {
      pass: true,
    };
  },

  toFailInvariant(actual, expected) {
    expect(actual).toThrowError(expected);
    return {
      pass: true,
    };
  },

  toWarn(actual, expected) {
    const negative = this.isNot;

    function formatItem(item) {
      return item instanceof RegExp ? item.toString() : JSON.stringify(item);
    }

    function formatArray(array) {
      return '[' + array.map(formatItem).join(', ') + ']';
    }

    function formatExpected(args) {
      return formatArray([false].concat(args));
    }

    function formatActual(calls) {
      if (calls.length) {
        return calls
          .map(args => {
            return formatArray([!!args[0]].concat(args.slice(1)));
          })
          .join(', ');
      } else {
        return '[]';
      }
    }

    const warning = require('warning');
    if (!warning.mock) {
      throw new Error("toWarn(): Requires `jest.mock('warning')`.");
    }

    const callsCount = warning.mock.calls.length;
    actual();
    const calls = warning.mock.calls.slice(callsCount);

    // Simple case: no explicit expectation.
    if (!expected) {
      const warned = calls.filter(args => !args[0]).length;
      return {
        pass: !!warned,
        message: () =>
          `Expected ${negative ? 'not ' : ''}to warn but ` +
          '`warning` received the following calls: ' +
          `${formatActual(calls)}.`,
      };
    }

    // Custom case: explicit expectation.
    if (!Array.isArray(expected)) {
      expected = [expected];
    }
    const call = calls.find(args => {
      return (
        args.length === expected.length + 1 &&
        args.every((arg, index) => {
          if (!index) {
            return !arg;
          }
          const other = expected[index - 1];
          return other instanceof RegExp ? other.test(arg) : arg === other;
        })
      );
    });

    return {
      pass: !!call,
      message: () =>
        `Expected ${negative ? 'not ' : ''}to warn: ` +
        `${formatExpected(expected)} but ` +
        '`warning` received the following calls: ' +
        `${formatActual(calls)}.`,
    };
  },

  toThrowTypeError(fn) {
    let pass = false;
    try {
      fn();
    } catch (e) {
      pass = e instanceof TypeError;
    }
    return {
      pass,
      message: () => 'Expected function to throw a TypeError.',
    };
  },
};

/**
 * Parses GraphQL text, applies the selected transforms only (or none if
 * transforms is not specified), and returns a mapping of definition name to
 * its basic generated representation.
 */
function generateWithTransforms(
  text: string,
  transforms?: ?Array<IRTransform>,
): {[key: string]: GeneratedNode} {
  const RelayTestSchema = require('./RelayTestSchema');
  return generate(text, RelayTestSchema, {
    commonTransforms: transforms || [],
    fragmentTransforms: [],
    queryTransforms: [],
    codegenTransforms: [],
    printTransforms: [],
  });
}

/**
 * Compiles the given GraphQL text using the standard set of transforms (as
 * defined in RelayCompiler) and returns a mapping of definition name to
 * its full runtime representation.
 */
function generateAndCompile(
  text: string,
  schema?: ?GraphQLSchema,
): {[key: string]: GeneratedNode} {
  const {IRTransforms} = require('relay-compiler');
  const RelayTestSchema = require('./RelayTestSchema');
  return generate(text, schema || RelayTestSchema, IRTransforms);
}

/**
 * Generates a set of jest snapshot tests that compare the output of the
 * provided `operation` to each of the matching files in the `fixturesPath`.
 * The fixture should have '# expected-to-throw' on its first line
 * if it is expected to fail
 */
function generateTestsFromFixtures(
  fixturesPath: string,
  operation: (input: string) => string | Promise<string>,
): void {
  const fs = require('fs');
  const path = require('path');
  it('matches expected output', async () => {
    const tests = fs.readdirSync(fixturesPath).map(async file => {
      const input = fs.readFileSync(path.join(fixturesPath, file), 'utf8');
      const output = await getOutputForFixture(input, operation, file);
      return {
        file,
        input,
        output,
      };
    });
    invariant(
      tests.length > 0,
      'generateTestsFromFixtures: No fixtures found at %s',
      fixturesPath,
    );
    const results = await Promise.all(tests);
    results.forEach(test => {
      expect({
        [FIXTURE_TAG]: true,
        input: test.input,
        output: test.output,
      }).toMatchSnapshot(test.file);
    });
  });
}

function generate(
  text: string,
  schema: GraphQLSchema,
  transforms: RelayCompilerTransforms,
): {[key: string]: GeneratedNode} {
  const {
    compileRelayArtifacts,
    GraphQLCompilerContext,
    IRTransforms,
    transformASTSchema,
  } = require('relay-compiler');

  const relaySchema = transformASTSchema(schema, IRTransforms.schemaExtensions);
  const compilerContext = new GraphQLCompilerContext(
    schema,
    relaySchema,
  ).addAll(parseGraphQLText(relaySchema, text).definitions);
  const documentMap = {};
  compileRelayArtifacts(compilerContext, transforms).forEach(
    ([_definition, node]) => {
      documentMap[
        node.kind === 'Request' ? node.params.name : node.name
      ] = node;
    },
  );
  return documentMap;
}

/**
 * A helper to create a deep clone of a value, plain Object, or array of such.
 *
 * Does not support RegExp, Date, other classes, or self-referential values.
 */
function simpleClone<T>(value: T): T {
  if (Array.isArray(value)) {
    return value.map(simpleClone);
  } else if (value != null && typeof value === 'object') {
    return ((mapObject(value, simpleClone): any): T);
  } else {
    return value;
  }
}

module.exports = {
  generateAndCompile,
  generateTestsFromFixtures,
  generateWithTransforms,
  matchers,
  simpleClone,
  FIXTURE_TAG,
};
<commit_msg>make snapshots separate tests<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

'use strict';

const getOutputForFixture = require('./getOutputForFixture');
const mapObject = require('mapObject');
const parseGraphQLText = require('./parseGraphQLText');

import type {GraphQLSchema} from 'graphql';
import type {RelayCompilerTransforms, IRTransform} from 'relay-compiler';
import type {GeneratedNode} from 'relay-runtime';

/* global expect,test */

/**
 * Extend Jest with a custom snapshot serializer to provide additional context
 * and reduce the amount of escaping that occurs.
 */
const FIXTURE_TAG = Symbol.for('FIXTURE_TAG');
expect.addSnapshotSerializer({
  print(value) {
    return Object.keys(value)
      .map(key => `~~~~~~~~~~ ${key.toUpperCase()} ~~~~~~~~~~\n${value[key]}`)
      .join('\n');
  },
  test(value) {
    return value && value[FIXTURE_TAG] === true;
  },
});

const matchers = {
  toBeDeeplyFrozen(actual) {
    function check(value) {
      expect(Object.isFrozen(value)).toBe(true);
      if (Array.isArray(value)) {
        value.forEach(item => check(item));
      } else if (typeof value === 'object' && value !== null) {
        for (const key in value) {
          check(value[key]);
        }
      }
    }
    check(actual);
    return {
      pass: true,
    };
  },

  toFailInvariant(actual, expected) {
    expect(actual).toThrowError(expected);
    return {
      pass: true,
    };
  },

  toWarn(actual, expected) {
    const negative = this.isNot;

    function formatItem(item) {
      return item instanceof RegExp ? item.toString() : JSON.stringify(item);
    }

    function formatArray(array) {
      return '[' + array.map(formatItem).join(', ') + ']';
    }

    function formatExpected(args) {
      return formatArray([false].concat(args));
    }

    function formatActual(calls) {
      if (calls.length) {
        return calls
          .map(args => {
            return formatArray([!!args[0]].concat(args.slice(1)));
          })
          .join(', ');
      } else {
        return '[]';
      }
    }

    const warning = require('warning');
    if (!warning.mock) {
      throw new Error("toWarn(): Requires `jest.mock('warning')`.");
    }

    const callsCount = warning.mock.calls.length;
    actual();
    const calls = warning.mock.calls.slice(callsCount);

    // Simple case: no explicit expectation.
    if (!expected) {
      const warned = calls.filter(args => !args[0]).length;
      return {
        pass: !!warned,
        message: () =>
          `Expected ${negative ? 'not ' : ''}to warn but ` +
          '`warning` received the following calls: ' +
          `${formatActual(calls)}.`,
      };
    }

    // Custom case: explicit expectation.
    if (!Array.isArray(expected)) {
      expected = [expected];
    }
    const call = calls.find(args => {
      return (
        args.length === expected.length + 1 &&
        args.every((arg, index) => {
          if (!index) {
            return !arg;
          }
          const other = expected[index - 1];
          return other instanceof RegExp ? other.test(arg) : arg === other;
        })
      );
    });

    return {
      pass: !!call,
      message: () =>
        `Expected ${negative ? 'not ' : ''}to warn: ` +
        `${formatExpected(expected)} but ` +
        '`warning` received the following calls: ' +
        `${formatActual(calls)}.`,
    };
  },

  toThrowTypeError(fn) {
    let pass = false;
    try {
      fn();
    } catch (e) {
      pass = e instanceof TypeError;
    }
    return {
      pass,
      message: () => 'Expected function to throw a TypeError.',
    };
  },
};

/**
 * Parses GraphQL text, applies the selected transforms only (or none if
 * transforms is not specified), and returns a mapping of definition name to
 * its basic generated representation.
 */
function generateWithTransforms(
  text: string,
  transforms?: ?Array<IRTransform>,
): {[key: string]: GeneratedNode} {
  const RelayTestSchema = require('./RelayTestSchema');
  return generate(text, RelayTestSchema, {
    commonTransforms: transforms || [],
    fragmentTransforms: [],
    queryTransforms: [],
    codegenTransforms: [],
    printTransforms: [],
  });
}

/**
 * Compiles the given GraphQL text using the standard set of transforms (as
 * defined in RelayCompiler) and returns a mapping of definition name to
 * its full runtime representation.
 */
function generateAndCompile(
  text: string,
  schema?: ?GraphQLSchema,
): {[key: string]: GeneratedNode} {
  const {IRTransforms} = require('relay-compiler');
  const RelayTestSchema = require('./RelayTestSchema');
  return generate(text, schema || RelayTestSchema, IRTransforms);
}

/**
 * Generates a set of jest snapshot tests that compare the output of the
 * provided `operation` to each of the matching files in the `fixturesPath`.
 * The fixture should have '# expected-to-throw' on its first line
 * if it is expected to fail
 */
function generateTestsFromFixtures(
  fixturesPath: string,
  operation: (input: string) => string | Promise<string>,
): void {
  const fs = require('fs');
  const path = require('path');

  const fixtures = fs.readdirSync(fixturesPath);

  test(`has fixtures in ${fixturesPath}`, () => {
    expect(fixtures.length > 0).toBe(true);
  });

  test.each(fixtures)('matches expected output: %s', async file => {
    const input = fs.readFileSync(path.join(fixturesPath, file), 'utf8');
    const output = await getOutputForFixture(input, operation, file);
    expect({
      [FIXTURE_TAG]: true,
      input,
      output,
    }).toMatchSnapshot();
  });
}

function generate(
  text: string,
  schema: GraphQLSchema,
  transforms: RelayCompilerTransforms,
): {[key: string]: GeneratedNode} {
  const {
    compileRelayArtifacts,
    GraphQLCompilerContext,
    IRTransforms,
    transformASTSchema,
  } = require('relay-compiler');

  const relaySchema = transformASTSchema(schema, IRTransforms.schemaExtensions);
  const compilerContext = new GraphQLCompilerContext(
    schema,
    relaySchema,
  ).addAll(parseGraphQLText(relaySchema, text).definitions);
  const documentMap = {};
  compileRelayArtifacts(compilerContext, transforms).forEach(
    ([_definition, node]) => {
      documentMap[
        node.kind === 'Request' ? node.params.name : node.name
      ] = node;
    },
  );
  return documentMap;
}

/**
 * A helper to create a deep clone of a value, plain Object, or array of such.
 *
 * Does not support RegExp, Date, other classes, or self-referential values.
 */
function simpleClone<T>(value: T): T {
  if (Array.isArray(value)) {
    return value.map(simpleClone);
  } else if (value != null && typeof value === 'object') {
    return ((mapObject(value, simpleClone): any): T);
  } else {
    return value;
  }
}

module.exports = {
  generateAndCompile,
  generateTestsFromFixtures,
  generateWithTransforms,
  matchers,
  simpleClone,
  FIXTURE_TAG,
};
<|endoftext|>


<commit_before>var ydsDemo = angular.module('ydsDemo', ['ui.router', 'ui.bootstrap', 'oc.lazyLoad', 'yds']);

ydsDemo.run(function($rootScope, $location) {
	$rootScope.location = $location;
});

// Controller to set base URL and project ID in Organisation buyer & seller demo pages
ydsDemo.controller('OrganisationController', ['$scope', '$location', function($scope, $location) {
	// Set base URL variable
	$scope.baseUrl = "http://ydsdev.iit.demokritos.gr/YDSComponents/#!/redirect";

	// Set language
	$scope.lang = "en";

	// Get project ID from url parameters
	var projectId = $location.search().id;
	if (_.isUndefined(projectId) || projectId.length == 0) {
		// Set default project ID to be able to display the page
		projectId = "http://linkedeconomy.org/resource/Organization/TED_730";

		console.warn("No project ID in URL parameters: using default ID");
	}

	$scope.projectId = projectId;
}]);

ydsDemo.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
	$urlRouterProvider.otherwise('/search');
	$locationProvider.hashPrefix('!');

	$stateProvider.state('search', {
		url: '/search',
		templateUrl: 'templates-demo/search.html'
	})
	.state('search-el', {
		url: '/search-el',
		templateUrl: 'templates-demo/search-el.html'
	})
	.state('search-tabbed', {
		url: '/search-tabbed',
		templateUrl: 'templates-demo/search-tabbed.html'
	})
	.state('dashboard', {
		url: '/dashboard',
		templateUrl: 'templates-demo/dashboard.html'
	})
	.state('dashboard2', {
		url: '/dashboard2',
		templateUrl: 'templates-demo/dashboard2.html'
	})
	.state('dashboardp1', {
		url: '/dashboardp1',
		templateUrl: 'templates-demo/dashboard-p1.html'
	})
	.state('public-works', {
		url: '/public-works',
		templateUrl: 'templates-demo/public-works.html'
	})
	.state('redirect', {
		url: '/redirect',
		template: '

Redirecting...

',
		controller: function($scope, $location) {
			// Check type url parameter and redirect to appropriate page
			var urlParams = $location.search();

			if (_.has(urlParams, "type")) {
				switch(urlParams.type) {
					case "Organisation.Buyer":
						$location.path("/organisation-buyer");
						break;
					case "Organisation.Seller":
						$location.path("/organisation-seller");
						break;
				}
			}
		}
	})
	.state('project2', {
		url: '/project2',
		templateUrl: 'templates-demo/project2.html'
	})
	.state('projects', {
		url: '/projects',
		templateUrl: 'templates-demo/resource.html',
		controller: function($scope) {
			$scope.disqusConfig = {
				disqus_shortname: 'ydscommentstest',
				disqus_identifier: 'projects',
				disqus_url: 'http://ydsdev.iit.demokritos.gr/YDSComponents/#!/projects'
			};
		}
	})
	.state('projects-el', {
		url: '/projects-el',
		templateUrl: 'templates-demo/resource-el.html'
	})
	.state('contract-ted', {
		url: '/contract-ted',
		templateUrl: 'templates-demo/contract-ted.html',
		controller: function($scope) {
			// Set project ID and base URL variables
			$scope.projectId = "http://linkedeconomy.org/resource/Contract/AwardNotice/2015233765/6910539";
			$scope.baseUrl = "http://ydsdev.iit.demokritos.gr/YDSComponents/#!/redirect";
		}
	})
	.state('organisation-buyer', {
		url: '/organisation-buyer',
		templateUrl: 'templates-demo/organisation-buyer.html',
		controller: 'OrganisationController'
	})
	.state('organisation-seller', {
		url: '/organisation-seller',
		templateUrl: 'templates-demo/organisation-seller.html',
		controller: 'OrganisationController'
	})
	.state('map', {
		url: '/map',
		templateUrl: 'templates-demo/map.html'
	})
	.state('geo-editing', {
		url: '/geo-editing',
		templateUrl: 'templates-demo/geo-editing.html'
	})
	.state('cache', {
		url: '/cache',
		templateUrl: 'templates-demo/cache-management.html'
	})
	.state('embed', {
		url: '/embed/:embedCode',
		template: '<yds-hybrid></yds-hybrid>'
	})
	.state('iframe', {
		url: '/iframe',
		templateUrl: 'templates-demo/iframe.html'
	})
	.state('browse', {
		url: '/browse',
		templateUrl: 'templates-demo/browse.html'
	})
	.state('workbench', {
		url: '/workbench',
		templateUrl: 'templates-demo/workbench.html'
	});
});

ydsDemo.run(function($rootScope, $templateCache) {
	$rootScope.$on('$stateChangeStart', function(event, next, current) {
		if (typeof(current) !== 'undefined'){
			$templateCache.remove(current.templateUrl);
		}
	});
});<commit_msg>Change default ID for organisation buyer/seller demo pages<commit_after>var ydsDemo = angular.module('ydsDemo', ['ui.router', 'ui.bootstrap', 'oc.lazyLoad', 'yds']);

ydsDemo.run(function($rootScope, $location) {
	$rootScope.location = $location;
});

// Controller to set base URL and project ID in Organisation buyer & seller demo pages
ydsDemo.controller('OrganisationController', ['$scope', '$location', function($scope, $location) {
	// Set base URL variable
	$scope.baseUrl = "http://ydsdev.iit.demokritos.gr/YDSComponents/#!/redirect";

	// Set language
	$scope.lang = "en";

	// Get project ID from url parameters
	var projectId = $location.search().id;
	if (_.isUndefined(projectId) || projectId.length == 0) {
		// Set default project ID to be able to display the page
		projectId = "http://linkedeconomy.org/resource/Organization/997687930";

		console.warn("No project ID in URL parameters: using default ID");
	}

	$scope.projectId = projectId;
}]);

ydsDemo.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
	$urlRouterProvider.otherwise('/search');
	$locationProvider.hashPrefix('!');

	$stateProvider.state('search', {
		url: '/search',
		templateUrl: 'templates-demo/search.html'
	})
	.state('search-el', {
		url: '/search-el',
		templateUrl: 'templates-demo/search-el.html'
	})
	.state('search-tabbed', {
		url: '/search-tabbed',
		templateUrl: 'templates-demo/search-tabbed.html'
	})
	.state('dashboard', {
		url: '/dashboard',
		templateUrl: 'templates-demo/dashboard.html'
	})
	.state('dashboard2', {
		url: '/dashboard2',
		templateUrl: 'templates-demo/dashboard2.html'
	})
	.state('dashboardp1', {
		url: '/dashboardp1',
		templateUrl: 'templates-demo/dashboard-p1.html'
	})
	.state('public-works', {
		url: '/public-works',
		templateUrl: 'templates-demo/public-works.html'
	})
	.state('redirect', {
		url: '/redirect',
		template: '

Redirecting...

',
		controller: function($scope, $location) {
			// Check type url parameter and redirect to appropriate page
			var urlParams = $location.search();

			if (_.has(urlParams, "type")) {
				switch(urlParams.type) {
					case "Organisation.Buyer":
						$location.path("/organisation-buyer");
						break;
					case "Organisation.Seller":
						$location.path("/organisation-seller");
						break;
				}
			}
		}
	})
	.state('project2', {
		url: '/project2',
		templateUrl: 'templates-demo/project2.html'
	})
	.state('projects', {
		url: '/projects',
		templateUrl: 'templates-demo/resource.html',
		controller: function($scope) {
			$scope.disqusConfig = {
				disqus_shortname: 'ydscommentstest',
				disqus_identifier: 'projects',
				disqus_url: 'http://ydsdev.iit.demokritos.gr/YDSComponents/#!/projects'
			};
		}
	})
	.state('projects-el', {
		url: '/projects-el',
		templateUrl: 'templates-demo/resource-el.html'
	})
	.state('contract-ted', {
		url: '/contract-ted',
		templateUrl: 'templates-demo/contract-ted.html',
		controller: function($scope) {
			// Set project ID and base URL variables
			$scope.projectId = "http://linkedeconomy.org/resource/Contract/AwardNotice/2015233765/6910539";
			$scope.baseUrl = "http://ydsdev.iit.demokritos.gr/YDSComponents/#!/redirect";
		}
	})
	.state('organisation-buyer', {
		url: '/organisation-buyer',
		templateUrl: 'templates-demo/organisation-buyer.html',
		controller: 'OrganisationController'
	})
	.state('organisation-seller', {
		url: '/organisation-seller',
		templateUrl: 'templates-demo/organisation-seller.html',
		controller: 'OrganisationController'
	})
	.state('map', {
		url: '/map',
		templateUrl: 'templates-demo/map.html'
	})
	.state('geo-editing', {
		url: '/geo-editing',
		templateUrl: 'templates-demo/geo-editing.html'
	})
	.state('cache', {
		url: '/cache',
		templateUrl: 'templates-demo/cache-management.html'
	})
	.state('embed', {
		url: '/embed/:embedCode',
		template: '<yds-hybrid></yds-hybrid>'
	})
	.state('iframe', {
		url: '/iframe',
		templateUrl: 'templates-demo/iframe.html'
	})
	.state('browse', {
		url: '/browse',
		templateUrl: 'templates-demo/browse.html'
	})
	.state('workbench', {
		url: '/workbench',
		templateUrl: 'templates-demo/workbench.html'
	});
});

ydsDemo.run(function($rootScope, $templateCache) {
	$rootScope.$on('$stateChangeStart', function(event, next, current) {
		if (typeof(current) !== 'undefined'){
			$templateCache.remove(current.templateUrl);
		}
	});
});<|endoftext|>