<commit_before>import { bindCallback, fromEvent, merge } from 'rxjs';
import { map, filter, startWith, pluck } from 'rxjs/operators';
import { glTF, ToneMaps, DebugOutput } from 'gltf-sample-viewer';
import { gltfInput } from '../input.js';

import { getIsGltf, getIsGlb, getIsHdr } from 'gltf-sample-viewer';

// this class wraps all the observables for the gltf sample viewer state
// the data streams coming out of this should match the data required in GltfState
// as close as possible
class UIModel
{
    constructor(app, modelPathProvider)
    {
        this.app = app;
        this.pathProvider = modelPathProvider;

        this.app.models = this.pathProvider.getAllKeys();

        const dropdownGltfChanged = app.modelChanged$.pipe(
            pluck("event", "msg"),
            startWith("Avocado"),
            map(value => {
                return this.pathProvider.resolve(value);
            }),
            map( value => ({mainFile: value, additionalFiles: undefined})),
        );
        this.flavour = app.flavourChanged$.pipe(pluck("event", "msg")); // TODO gltfModelPathProvider needs to be changed to accept flavours explicitely
        this.scene = app.sceneChanged$.pipe(pluck("event", "msg"));
        this.camera = app.cameraChanged$.pipe(pluck("event", "msg"));
        this.environment = app.environmentChanged$.pipe(pluck("event", "msg"));
        this.environmentRotation = app.environmentRotationChanged$.pipe(pluck("event", "msg"));

        this.app.tonemaps = Object.keys(ToneMaps).map((key) => {
            return {title: ToneMaps[key]};
        });
        this.tonemap = app.tonemapChanged$.pipe(
            pluck("event", "msg"),
            startWith(ToneMaps.LINEAR)
        );

        this.app.debugchannels = Object.keys(DebugOutput).map((key) => {
            return {title: DebugOutput[key]};
        });
        this.debugchannel = app.debugchannelChanged$.pipe(
            pluck("event", "msg"),
            startWith(DebugOutput.NONE)
        );

        this.exposurecompensation = app.exposureChanged$.pipe(pluck("event", "msg"));
        this.skinningEnabled = app.skinningChanged$.pipe(pluck("event", "msg"));
        this.morphingEnabled = app.morphingChanged$.pipe(pluck("event", "msg"));
        this.clearcoatEnabled = app.clearcoatChanged$.pipe(pluck("event", "msg"));
        this.sheenEnabled = app.sheenChanged$.pipe(pluck("event", "msg"));
        this.transmissionEnabled = app.transmissionChanged$.pipe(pluck("event", "msg"));
        this.iblEnabled = app.iblChanged$.pipe(pluck("event", "msg"));
        this.punctualLightsEnabled = app.punctualLightsChanged$.pipe(pluck("event", "msg"));
        this.environmentEnabled = app.environmentVisibilityChanged$.pipe(pluck("event", "msg"));
        this.addEnvironment = app.addEnvironment$.pipe(map(() => {/* TODO Open file dialog */}));

        const initialClearColor = "#303542";
        this.app.clearColor = initialClearColor;
        this.clearColor = app.colorChanged$.pipe(
            filter(value => value.event !== undefined),
            pluck("event", "msg"),
            startWith(initialClearColor),
            map(hex => {
                // convert hex string to rgb values
                var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
                return result ? [
                    parseInt(result[1], 16),
                    parseInt(result[2], 16),
                    parseInt(result[3], 16)
                ] : null;
            })
        );

        this.animationPlay = app.animationPlayChanged$.pipe(pluck("event", "msg"));

        const inputObservables = UIModel.getInputObservables(document.getElementById("canvas"));
        this.model = merge(dropdownGltfChanged, inputObservables.gltfDropped);
        this.hdr = inputObservables.hdrDropped;

        this.variant = app.variantChanged$.pipe(pluck("event", "msg"));
    }

    static getInputObservables(inputDomElement)
    {
        const observables = {};
        fromEvent(inputDomElement, "dragover").subscribe( event => event.preventDefault() ); // just prevent the default behaviour
        const dropEvent = fromEvent(inputDomElement, "drop").pipe( map( event => {
            // prevent the default drop event
            event.preventDefault();
            return event;
        }));
        observables.filesDropped = dropEvent.pipe(map( (event) => {
            // Use DataTransfer files interface to access the file(s)
            return Array.from(event.dataTransfer.files);
        }));
        observables.gltfDropped = observables.filesDropped.pipe(
            // filter out any non .gltf or .glb files
            filter( (files) => files.filter( file => getIsGlb(file.name) || getIsGltf(file.name)).length > 0),
            map( (files) => {
                // restructure the data by separating mainFile (gltf/glb) from additionalFiles
                const mainFile = files.find( (file) => getIsGlb(file.name) || getIsGltf(file.name));
                const additionalFiles = files.filter( (file) => file !== mainFile);
                return {mainFile: mainFile, additionalFiles: additionalFiles};
            }),
            filter(files => files.mainFile !== undefined),
        );
        observables.hdrDropped = observables.filesDropped.pipe(
            map( (files) => {
                // extract only the hdr file from the stream of files
                return files.find( (file) => file.name.endsWith(".hdr"));
            }),
            filter(file => file !== undefined),
        )
        return observables;
    }

    attachGltfLoaded(glTFLoadedStateObservable)
    {
        const gltfLoadedAndInit = glTFLoadedStateObservable.pipe(
            map( state => state.gltf ),
            startWith(new glTF())
        );

        // update scenes
        const sceneIndices = gltfLoadedAndInit.pipe(
            map( (gltf) => {
                return gltf.scenes.map( (scene, index) => {
                    let name = scene.name;
                    if(name === "" || name === undefined)
                    {
                        name = index;
                    }
                    return {title: name, index: index};
                });
            })
        );
        sceneIndices.subscribe( (scenes) => {
            this.app.scenes = scenes;
        });

        const loadedSceneIndex = glTFLoadedStateObservable.pipe(
            map( (state) => state.sceneIndex)
        );
        loadedSceneIndex.subscribe( (scene) => {
            this.app.selectedScene = scene;
        });

        // update cameras
        this.attachCameraChangeObservable(glTFLoadedStateObservable);

        const variants = gltfLoadedAndInit.pipe(
            map( (gltf) => {
                if(gltf.variants !== undefined)
                {
                    return gltf.variants.map( (variant, index) => {
                        return {title: variant.name};
                    });
                }
                return [];
            }),
            map(variants => {
                // Add a "None" variant to the beginning
                variants.unshift({title: "None"});
                return variants;
            })
        );
        variants.subscribe( (variants) => {
            this.app.materialVariants = variants;
        });

        gltfLoadedAndInit.subscribe(
            (_) => {this.app.setAnimationState(true);
            }
        );

        const xmpData = gltfLoadedAndInit.pipe(
            map( (gltf) => {
                if(gltf.extensions !== undefined && gltf.extensions.KHR_xmp !== undefined)
                {
                    if(gltf.asset.extensions !== undefined && gltf.asset.extensions.KHR_xmp !== undefined)
                    {
                        let xmpPacket = gltf.extensions.KHR_xmp.packets[gltf.asset.extensions.KHR_xmp.packet];
                        return {xmp: xmpPacket};
                    }
                }
                return [];
            })
        );
        xmpData.subscribe( (xmpData) => {
            this.app.xmp = xmpData;
        });
    }

    updateStatistics(statisticsUpdateObservable)
    {
        statisticsUpdateObservable.subscribe(
            data => {this.app.statistics = [
                {title: "Mesh Count", value: data.meshCount},
                {title: "Triangle Count", value: data.faceCount},
                {title: "Opaque Material Count", value: data.opaqueMaterialsCount},
                {title: "Transparent Material Count", value: data.transparentMaterialsCount}
            ];
            }
        );
    }

    attachCameraChangeObservable(sceneChangeObservable)
    {
        const cameraIndices = sceneChangeObservable.pipe(
            map( (state) => {
                let gltf = state.gltf;
                let cameraIndices = [{title: "User Camera", index: -1}];
                cameraIndices.push(...gltf.cameras.map( (camera, index) => {
                    if(gltf.scenes[state.sceneIndex].includesNode(gltf, camera.node))
                    {
                        let name = camera.name;
                        if(name === "" || name === undefined)
                        {
                            name = index;
                        }
                        return {title: name, index: index};
                    }
                }));
                cameraIndices = cameraIndices.filter(function(el) {
                    return el !== undefined;
                });
                return cameraIndices;
            })
        );
        cameraIndices.subscribe( (cameras) => {
            this.app.cameras = cameras;
        });
        const loadedCameraIndex = sceneChangeObservable.pipe(
            map( (state) => {
                return state.cameraIndex;
            })
        );
        loadedCameraIndex.subscribe( index => {
            if(index ===  undefined)
            {
                index = -1;
            }
            this.app.selectedCamera = index;
        });
    }
}

export { UIModel };
<commit_msg>dropping a gltf now updates the UI<commit_after>import { bindCallback, fromEvent, merge } from 'rxjs';
import { map, filter, startWith, pluck } from 'rxjs/operators';
import { glTF, ToneMaps, DebugOutput } from 'gltf-sample-viewer';
import { gltfInput } from '../input.js';

import { getIsGltf, getIsGlb, getIsHdr } from 'gltf-sample-viewer';

// this class wraps all the observables for the gltf sample viewer state
// the data streams coming out of this should match the data required in GltfState
// as close as possible
class UIModel
{
    constructor(app, modelPathProvider)
    {
        this.app = app;
        this.pathProvider = modelPathProvider;

        this.app.models = this.pathProvider.getAllKeys();

        const dropdownGltfChanged = app.modelChanged$.pipe(
            pluck("event", "msg"),
            startWith("Avocado"),
            map(value => {
                return this.pathProvider.resolve(value);
            }),
            map( value => ({mainFile: value, additionalFiles: undefined})),
        );
        this.flavour = app.flavourChanged$.pipe(pluck("event", "msg")); // TODO gltfModelPathProvider needs to be changed to accept flavours explicitely
        this.scene = app.sceneChanged$.pipe(pluck("event", "msg"));
        this.camera = app.cameraChanged$.pipe(pluck("event", "msg"));
        this.environment = app.environmentChanged$.pipe(pluck("event", "msg"));
        this.environmentRotation = app.environmentRotationChanged$.pipe(pluck("event", "msg"));

        this.app.tonemaps = Object.keys(ToneMaps).map((key) => {
            return {title: ToneMaps[key]};
        });
        this.tonemap = app.tonemapChanged$.pipe(
            pluck("event", "msg"),
            startWith(ToneMaps.LINEAR)
        );

        this.app.debugchannels = Object.keys(DebugOutput).map((key) => {
            return {title: DebugOutput[key]};
        });
        this.debugchannel = app.debugchannelChanged$.pipe(
            pluck("event", "msg"),
            startWith(DebugOutput.NONE)
        );

        this.exposurecompensation = app.exposureChanged$.pipe(pluck("event", "msg"));
        this.skinningEnabled = app.skinningChanged$.pipe(pluck("event", "msg"));
        this.morphingEnabled = app.morphingChanged$.pipe(pluck("event", "msg"));
        this.clearcoatEnabled = app.clearcoatChanged$.pipe(pluck("event", "msg"));
        this.sheenEnabled = app.sheenChanged$.pipe(pluck("event", "msg"));
        this.transmissionEnabled = app.transmissionChanged$.pipe(pluck("event", "msg"));
        this.iblEnabled = app.iblChanged$.pipe(pluck("event", "msg"));
        this.punctualLightsEnabled = app.punctualLightsChanged$.pipe(pluck("event", "msg"));
        this.environmentEnabled = app.environmentVisibilityChanged$.pipe(pluck("event", "msg"));
        this.addEnvironment = app.addEnvironment$.pipe(map(() => {/* TODO Open file dialog */}));

        const initialClearColor = "#303542";
        this.app.clearColor = initialClearColor;
        this.clearColor = app.colorChanged$.pipe(
            filter(value => value.event !== undefined),
            pluck("event", "msg"),
            startWith(initialClearColor),
            map(hex => {
                // convert hex string to rgb values
                var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
                return result ? [
                    parseInt(result[1], 16),
                    parseInt(result[2], 16),
                    parseInt(result[3], 16)
                ] : null;
            })
        );

        this.animationPlay = app.animationPlayChanged$.pipe(pluck("event", "msg"));

        const inputObservables = UIModel.getInputObservables(document.getElementById("canvas"));
        this.model = merge(dropdownGltfChanged, inputObservables.gltfDropped);
        this.hdr = inputObservables.hdrDropped;

        this.variant = app.variantChanged$.pipe(pluck("event", "msg"));

        this.model.subscribe(() => {
            // remove last filename
            if(this.app.models[this.app.models.length -1] === this.lastDroppedFilename)
            {
                this.app.models.pop();
                this.lastDroppedFilename = undefined;
            }
        });

        const dropedFileName = inputObservables.gltfDropped.pipe(
            map( (data) => {
                return data.mainFile.name;
            })
        );
        dropedFileName.subscribe( (filename) => {
            if(filename !== undefined)
            {
                filename = filename.split('/').pop();
                filename = filename.substr(0, filename.lastIndexOf('.'));

                this.app.models.push(filename);
                this.app.selectedModel = filename;
                this.lastDroppedFilename = filename;
            }
        });
    }

    static getInputObservables(inputDomElement)
    {
        const observables = {};
        fromEvent(inputDomElement, "dragover").subscribe( event => event.preventDefault() ); // just prevent the default behaviour
        const dropEvent = fromEvent(inputDomElement, "drop").pipe( map( event => {
            // prevent the default drop event
            event.preventDefault();
            return event;
        }));
        observables.filesDropped = dropEvent.pipe(map( (event) => {
            // Use DataTransfer files interface to access the file(s)
            return Array.from(event.dataTransfer.files);
        }));
        observables.gltfDropped = observables.filesDropped.pipe(
            // filter out any non .gltf or .glb files
            filter( (files) => files.filter( file => getIsGlb(file.name) || getIsGltf(file.name)).length > 0),
            map( (files) => {
                // restructure the data by separating mainFile (gltf/glb) from additionalFiles
                const mainFile = files.find( (file) => getIsGlb(file.name) || getIsGltf(file.name));
                const additionalFiles = files.filter( (file) => file !== mainFile);
                return {mainFile: mainFile, additionalFiles: additionalFiles};
            }),
            filter(files => files.mainFile !== undefined),
        );
        observables.hdrDropped = observables.filesDropped.pipe(
            map( (files) => {
                // extract only the hdr file from the stream of files
                return files.find( (file) => file.name.endsWith(".hdr"));
            }),
            filter(file => file !== undefined),
        )
        return observables;
    }

    attachGltfLoaded(glTFLoadedStateObservable)
    {
        const gltfLoadedAndInit = glTFLoadedStateObservable.pipe(
            map( state => state.gltf ),
            startWith(new glTF())
        );

        // update scenes
        const sceneIndices = gltfLoadedAndInit.pipe(
            map( (gltf) => {
                return gltf.scenes.map( (scene, index) => {
                    let name = scene.name;
                    if(name === "" || name === undefined)
                    {
                        name = index;
                    }
                    return {title: name, index: index};
                });
            })
        );
        sceneIndices.subscribe( (scenes) => {
            this.app.scenes = scenes;
        });

        const loadedSceneIndex = glTFLoadedStateObservable.pipe(
            map( (state) => state.sceneIndex)
        );
        loadedSceneIndex.subscribe( (scene) => {
            this.app.selectedScene = scene;
        });

        // update cameras
        this.attachCameraChangeObservable(glTFLoadedStateObservable);

        const variants = gltfLoadedAndInit.pipe(
            map( (gltf) => {
                if(gltf.variants !== undefined)
                {
                    return gltf.variants.map( (variant, index) => {
                        return {title: variant.name};
                    });
                }
                return [];
            }),
            map(variants => {
                // Add a "None" variant to the beginning
                variants.unshift({title: "None"});
                return variants;
            })
        );
        variants.subscribe( (variants) => {
            this.app.materialVariants = variants;
        });

        gltfLoadedAndInit.subscribe(
            (_) => {this.app.setAnimationState(true);
            }
        );

        const xmpData = gltfLoadedAndInit.pipe(
            map( (gltf) => {
                if(gltf.extensions !== undefined && gltf.extensions.KHR_xmp !== undefined)
                {
                    if(gltf.asset.extensions !== undefined && gltf.asset.extensions.KHR_xmp !== undefined)
                    {
                        let xmpPacket = gltf.extensions.KHR_xmp.packets[gltf.asset.extensions.KHR_xmp.packet];
                        return {xmp: xmpPacket};
                    }
                }
                return [];
            })
        );
        xmpData.subscribe( (xmpData) => {
            this.app.xmp = xmpData;
        });
    }

    updateStatistics(statisticsUpdateObservable)
    {
        statisticsUpdateObservable.subscribe(
            data => {this.app.statistics = [
                {title: "Mesh Count", value: data.meshCount},
                {title: "Triangle Count", value: data.faceCount},
                {title: "Opaque Material Count", value: data.opaqueMaterialsCount},
                {title: "Transparent Material Count", value: data.transparentMaterialsCount}
            ];
            }
        );
    }

    attachCameraChangeObservable(sceneChangeObservable)
    {
        const cameraIndices = sceneChangeObservable.pipe(
            map( (state) => {
                let gltf = state.gltf;
                let cameraIndices = [{title: "User Camera", index: -1}];
                cameraIndices.push(...gltf.cameras.map( (camera, index) => {
                    if(gltf.scenes[state.sceneIndex].includesNode(gltf, camera.node))
                    {
                        let name = camera.name;
                        if(name === "" || name === undefined)
                        {
                            name = index;
                        }
                        return {title: name, index: index};
                    }
                }));
                cameraIndices = cameraIndices.filter(function(el) {
                    return el !== undefined;
                });
                return cameraIndices;
            })
        );
        cameraIndices.subscribe( (cameras) => {
            this.app.cameras = cameras;
        });
        const loadedCameraIndex = sceneChangeObservable.pipe(
            map( (state) => {
                return state.cameraIndex;
            })
        );
        loadedCameraIndex.subscribe( index => {
            if(index ===  undefined)
            {
                index = -1;
            }
            this.app.selectedCamera = index;
        });
    }
}

export { UIModel };
<|endoftext|>


<commit_before>'use strict'

const ws = require('ws')
const WebSocketServer = ws.Server
const config = require('./config')
const bluebird = require('bluebird')
const fs = require('fs')
const Router = require('./core/router')
const async = require('async')
const PlatformClass = require('./platforms/' + config.platform)
const errcode = require('./core/errcode')
const co = require('co')
const cluster = require('cluster')
const os = require('os')
const log = require('./core/log')
const utils = require('./core/utils')
const exec = require('child_process').exec

const promisify = [ 'fs', 'mongodb' ]
for (const moduleName of promisify)
{
	const module = require(moduleName)
	bluebird.promisifyAll(module)
}

class MetagameServer
{
	constructor()
	{
		this.log = log.create('server')
		this.platform = new PlatformClass()
	}

	*initWorker()
	{
		this.server = new WebSocketServer({
			port: config.websocket.port
		})

		const router = new Router(this.server)

		const servicesDir = './services/'
		const files = yield fs.readdirAsync(servicesDir)
		for (const file of files)
		{
			const serviceName = file.split('.')[0]
			const ServiceClass = require(servicesDir + serviceName)

			const serviceLogger = log.create(serviceName)
			const service = new ServiceClass({
				log: serviceLogger,
				platform: this.platform,
				router,
			})

			if (service.init)
			{
				yield service.init()
			}

			const routes = service.getRoutes()
			for (const route of routes)
			{
				const path = route[0]
				const handler = route[1].bind(service)
				let middleware = route[2]
				if (middleware)
				{
					middleware = middleware.map(func => func.bind(service))
				}
				router.addRoute(path, handler, middleware)
			}
		}

		router.start()
		this.log.info('init done')
	}

	*init()
	{
		if (cluster.isMaster && config.clustering.enabled)
		{
			const coreCount = os.cpus().length
			const workerCount = coreCount * config.clustering.workersPerCore
			this.log.info({
				coreCount,
				workerCount
			}, 'forking')

			for (let i = 0; i < workerCount; i++)
			{
				cluster.fork({ version: config.version })
			}
		}
		else
		{
			yield this.initWorker()
		}
	}

	close()
	{
		this.server.close()
	}

	get address()
	{
		return this.server._server.address()
	}
}

if (module.parent)
{
	module.exports = MetagameServer
}
else
{
	co(function*()
	{
		const server = new MetagameServer()
		yield server.init()
	}).catch(err => { throw err })
}<commit_msg>Store reference to http server<commit_after>'use strict'

const ws = require('ws')
const WebSocketServer = ws.Server
const config = require('./config')
const bluebird = require('bluebird')
const fs = require('fs')
const Router = require('./core/router')
const async = require('async')
const PlatformClass = require('./platforms/' + config.platform)
const errcode = require('./core/errcode')
const co = require('co')
const cluster = require('cluster')
const os = require('os')
const log = require('./core/log')
const utils = require('./core/utils')
const exec = require('child_process').exec

const promisify = [ 'fs', 'mongodb' ]
for (const moduleName of promisify)
{
	const module = require(moduleName)
	bluebird.promisifyAll(module)
}

class MetagameServer
{
	constructor()
	{
		this.log = log.create('server')
		this.platform = new PlatformClass()
	}

	*initWorker()
	{
		this.webSocketServer = new WebSocketServer({
			port: config.websocket.port
		})

		this.httpServer = this.webSocketServer._server
		const router = new Router(this.webSocketServer)

		const servicesDir = './services/'
		const files = yield fs.readdirAsync(servicesDir)
		for (const file of files)
		{
			const serviceName = file.split('.')[0]
			const ServiceClass = require(servicesDir + serviceName)

			const serviceLogger = log.create(serviceName)
			const service = new ServiceClass({
				log: serviceLogger,
				platform: this.platform,
				router,
			})

			if (service.init)
			{
				yield service.init()
			}

			const routes = service.getRoutes()
			for (const route of routes)
			{
				const path = route[0]
				const handler = route[1].bind(service)
				let middleware = route[2]
				if (middleware)
				{
					middleware = middleware.map(func => func.bind(service))
				}
				router.addRoute(path, handler, middleware)
			}
		}

		router.start()
		this.log.info('init done')
	}

	*init()
	{
		if (cluster.isMaster && config.clustering.enabled)
		{
			const coreCount = os.cpus().length
			const workerCount = coreCount * config.clustering.workersPerCore
			this.log.info({
				coreCount,
				workerCount
			}, 'forking')

			for (let i = 0; i < workerCount; i++)
			{
				cluster.fork({ version: config.version })
			}
		}
		else
		{
			yield this.initWorker()
		}
	}

	close()
	{
		this.webSocketServer.close()
	}

	get address()
	{
		return this.httpServer.address()
	}
}

if (module.parent)
{
	module.exports = MetagameServer
}
else
{
	co(function*()
	{
		const server = new MetagameServer()
		yield server.init()
	}).catch(err => { throw err })
}<|endoftext|>


<commit_before>import assert from 'assert';
import Promise from 'bluebird';
import { helpers } from 'mostly-feathers-mongoose';
import fp from 'mostly-func';
import makeDebug from 'debug';
import path from 'path';
import url from 'url';

import { DocTypes, Permissions } from '../constants';
import { getAces, getParentAces } from '../helpers';

const debug = makeDebug('playing:content-services:hooks:documentEnrichers');

// check whether there is any folder children
function hasFolderishChild (hook, docs, options) {
  // only Folderish need to check hasFolderishChild
  const Types = options.DocTypes || DocTypes;
  const folders = fp.reduce((ids, doc) => {
    if (fp.contains('Folderish', Types[doc.type] && Types[doc.type].facets || [])) {
      return ids.concat(doc.id);
    }
    return ids;
  }, [], docs);

  const svcFolders = hook.app.service('folders');
  const getParents = folders.length > 0
    ? svcFolders.find({ query: { parent: { $in: folders } } })
    : Promise.resolve([]);

  return getParents.then(results => {
    results = results? results.data || results : [];
    const childrens = fp.groupBy(child => String(child.parent), results);
    return fp.reduce((acc, doc) => {
      const folderishChildren = fp.filter(child => {
        return fp.contains('Folderish', Types[child.type] && Types[child.type].facets || []);
      }, childrens[doc.id] || []);
      acc[doc.id] = folderishChildren.length > 0;
      return acc;
    }, {}, docs);
  });
}

function getBreadcrumbs (hook, docs, options) {
  const svcDocuments = hook.app.service('documents');

  let ancestorRready = true; // whether already populated
  const ancestors = fp.reject(fp.isNil, fp.flatMap(doc => {
    return fp.map(parent => {
      ancestorRready &= fp.has('type', parent);
      return fp.has('type', parent)? parent : null;
    }, doc.ancestors || []);
  }, docs));

  const ancestorIds = fp.reject(fp.isNil, fp.flatMap(doc => {
    return fp.map(parent => {
      return fp.has('type', parent)? null : helpers.getId(parent);
    }, doc.ancestors || []);
  }, docs));

  const getAncestors = () => {
    if (ancestorIds.length > 0) {
      // with document creating permission/subtypes in current breadcumb
      return svcDocuments.find({
        query: { _id: { $in: ancestorIds } },
        headers: { 'enrichers-document': 'permissions,subtypes' },
        paginate: false
      });
    } else {
      return Promise.resolve();
    }
  };

  return getAncestors().then(results => {
    results = results? results.data || results : [];
    const breads = fp.map(
      fp.pick(['id', 'path', 'title', 'metadata']),
      fp.concat(ancestors, results)
    );
    return fp.reduce((acc, doc) => {
      const breadcrumbs = fp.map(parent => {
        return fp.find(fp.propEq('id', helpers.getId(parent)), breads);
      }, doc.ancestors || []);
      acc[doc.id] = breadcrumbs;
      return acc;
    }, {}, docs);
  });
}

function getCollections (hook, docs, options) {
  const svcDocuments = hook.app.service('documents');
  const svcUserCollections = hook.app.service('user-collections');
  if (!hook.params.user) return Promise.resolve({});

  return svcUserCollections.find({
    query: {
      user: hook.params.user.id,
      document: { $in: fp.map(fp.prop('id'), docs) },
      $select: 'collect,*'
    },
    paginate: false
  }).then(results => {
    results = results? results.data || results : [];
    const documents = fp.groupBy(fp.prop('id'), results);
    return fp.reduce((acc, doc) => {
      if (documents[doc.id] && documents[doc.id].length > 0) {
        acc[doc.id] = documents[doc.id][0].collect;
      }
      return acc;
    }, {}, docs);
  });
}

function getFavorites (hook, docs, options) {
  const svcUserFavorites = hook.app.service('user-favorites');
  if (!hook.params.user) return Promise.resolve();

  return svcUserFavorites.find({
    query: {
      user: hook.params.user.id,
      document: { $in: fp.map(fp.prop('id'), docs) },
    },
    paginate: false
  }).then((results) => {
    results = results? results.data || results : [];
    const documents = fp.groupBy(fp.prop('id'));
    return fp.reduce((acc, doc) => {
      acc[doc.id] = { isFavorite: documents[doc.id] && documents[doc.id].length > 0 || false };
      return acc;
    }, {}, docs);
  });
}

function getAcls (hook, docs, options) {
  const inheritedDocs = fp.filter(fp.propEq('inherited', true), docs);
  return Promise.all([
    getAces(hook.app, docs),
    getParentAces(hook.app, inheritedDocs)
  ]).then(([localAces, inheritedAces]) => {
    const aces = fp.reduce((acc, doc) => {
      acc[doc.id] = acc[doc.id] || [];
      if (localAces && localAces[doc.id] && localAces[doc.id].length > 0) {
        acc[doc.id].push({ name: 'local', aces: localAces[doc.id] });
      }
      return acc;
    }, {}, docs);
    return fp.reduce((acc, doc) => {
      acc[doc.id] = acc[doc.id] || [];
      if (inheritedAces && inheritedAces[doc.id] && inheritedAces[doc.id].length > 0) {
        acc[doc.id].push({ name: 'inherited', aces: inheritedAces[doc.id] });
      }
      return acc;
    }, aces, docs);
  });
}

function getPermission (hook, docs, options) {
  const Types = options.DocTypes || DocTypes;
  return Promise.resolve(fp.reduce((acc, doc) => {
    const subtypes = Types[doc.type] && Types[doc.type].subtypes;
    // TODO: user acls
    let permissions = ['Everything', 'Read', 'Write', 'ReadWrite'];
    if (subtypes) {
      permissions = permissions.concat(['ReadChildren', 'AddChildren', 'RemoveChildren']);
    }
    acc[doc.id] = permissions;
    return acc;
  }, {}, docs));
}

function getUserVisiblePermissions (hook, docs, options) {
  return Promise.resolve(fp.reduce((acc, doc) => {
    acc[doc.id] = ['Read', 'ReadWrite', 'Everything'];
    return acc;
  }, {}, docs));
}

function getSubtypes (hook, docs, options) {
  const Types = options.DocTypes || DocTypes;
  return Promise.resolve(fp.reduce((acc, doc) => {
    const subtypes = Types[doc.type] && Types[doc.type].subtypes;
    acc[doc.id] = subtypes? Object.values(fp.pick(subtypes, Types)) : [];
    return acc;
  }, {}, docs));
}

function getTags (hook, docs, options) {
  return Promise.resolve(fp.reduce((acc, doc) => {
    acc[doc.id] = doc.tags || [];
    return acc;
  }, {}, docs));
}

function getThumbnail (hook, docs) {
  const baseUrl = 'bower_components/playing-content-elements/images/icons/';
  return Promise.resolve(fp.reduce((acc, doc) => {
    acc[doc.id] = {
      url: url.resolve(baseUrl, doc.type + '.png')
    };
    return acc;
  }, {}, docs));
}

// Add document metadata according to request header
export default function documentEnrichers (options = {}) {
  return (hook) => {
    assert(hook.type === 'after', `documentEnrichers must be used as a 'after' hook.`);

    const documentEnrichers = hook.params &&
      (hook.params.headers && hook.params.headers['enrichers-document']);

      // If no enrichers-document header then skip this hook
    if (!documentEnrichers) {
      debug('Skip documentEnrichers without headers', hook.params.query);
      return hook;
    }

    const enrichers = fp.map(fp.trim, fp.split(',', documentEnrichers));
    debug('enrichers-document %j', enrichers);

    let documents = helpers.getHookDataAsArray(hook);
    let promises = {};

    enrichers.forEach(enricher => {
      switch(enricher) {
        case 'acls':
          promises.acls = getAcls(hook, documents, options);
          break;
        case 'breadcrumbs':
          promises.breadcrumbs = getBreadcrumbs(hook, documents, options);
          break;
        case 'collections':
          promises.collections = getCollections(hook, documents, options);
          break;
        case 'favorites':
          promises.favorites = getFavorites(hook, documents, options);
          break;
        case 'hasFolderishChild':
          promises.hasFolderishChild = hasFolderishChild(hook, documents, options);
          break;
        case 'permissions':
          promises.permissions = getPermission(hook, documents, options);
          break;
        case 'preview':
          promises.preview = Promise.resolve(null);
          break;
        case 'renditions':
          promises.renditions = Promise.resolve(null);
          break;
        case 'subtypes':
          promises.subtypes = getSubtypes(hook, documents, options);
          break;
        case 'tags':
          promises.tags = getTags(hook, documents, options);
          break;
        case 'thumbnail':
          promises.thumbnail = getThumbnail(hook, documents, options);
          break;
        case 'userVisiblePermissions':
          promises.userVisiblePermissions = getUserVisiblePermissions(hook, documents, options);
          break;
      }
    });

    return Promise.props(promises).then(results => {
      documents.forEach(doc => {
        doc.metadata = doc.metadata || {};
        for (const enricher of Object.keys(results)) {
          if (results[enricher]) {
            doc.metadata[enricher] = results[enricher][doc.id];
          }
        }
        doc.metadata = fp.sortKeys(doc.metadata);
      });
      return hook;
    });
  };
}
<commit_msg>rename user-collection `subject`<commit_after>import assert from 'assert';
import Promise from 'bluebird';
import { helpers } from 'mostly-feathers-mongoose';
import fp from 'mostly-func';
import makeDebug from 'debug';
import path from 'path';
import url from 'url';

import { DocTypes, Permissions } from '../constants';
import { getAces, getParentAces } from '../helpers';

const debug = makeDebug('playing:content-services:hooks:documentEnrichers');

// check whether there is any folder children
function hasFolderishChild (hook, docs, options) {
  // only Folderish need to check hasFolderishChild
  const Types = options.DocTypes || DocTypes;
  const folders = fp.reduce((ids, doc) => {
    if (fp.contains('Folderish', Types[doc.type] && Types[doc.type].facets || [])) {
      return ids.concat(doc.id);
    }
    return ids;
  }, [], docs);

  const svcFolders = hook.app.service('folders');
  const getParents = folders.length > 0
    ? svcFolders.find({ query: { parent: { $in: folders } } })
    : Promise.resolve([]);

  return getParents.then(results => {
    results = results? results.data || results : [];
    const childrens = fp.groupBy(child => String(child.parent), results);
    return fp.reduce((acc, doc) => {
      const folderishChildren = fp.filter(child => {
        return fp.contains('Folderish', Types[child.type] && Types[child.type].facets || []);
      }, childrens[doc.id] || []);
      acc[doc.id] = folderishChildren.length > 0;
      return acc;
    }, {}, docs);
  });
}

function getBreadcrumbs (hook, docs, options) {
  const svcDocuments = hook.app.service('documents');

  let ancestorRready = true; // whether already populated
  const ancestors = fp.reject(fp.isNil, fp.flatMap(doc => {
    return fp.map(parent => {
      ancestorRready &= fp.has('type', parent);
      return fp.has('type', parent)? parent : null;
    }, doc.ancestors || []);
  }, docs));

  const ancestorIds = fp.reject(fp.isNil, fp.flatMap(doc => {
    return fp.map(parent => {
      return fp.has('type', parent)? null : helpers.getId(parent);
    }, doc.ancestors || []);
  }, docs));

  const getAncestors = () => {
    if (ancestorIds.length > 0) {
      // with document creating permission/subtypes in current breadcumb
      return svcDocuments.find({
        query: { _id: { $in: ancestorIds } },
        headers: { 'enrichers-document': 'permissions,subtypes' },
        paginate: false
      });
    } else {
      return Promise.resolve();
    }
  };

  return getAncestors().then(results => {
    results = results? results.data || results : [];
    const breads = fp.map(
      fp.pick(['id', 'path', 'title', 'metadata']),
      fp.concat(ancestors, results)
    );
    return fp.reduce((acc, doc) => {
      const breadcrumbs = fp.map(parent => {
        return fp.find(fp.propEq('id', helpers.getId(parent)), breads);
      }, doc.ancestors || []);
      acc[doc.id] = breadcrumbs;
      return acc;
    }, {}, docs);
  });
}

function getCollections (hook, docs, options) {
  const svcDocuments = hook.app.service('documents');
  const svcUserCollections = hook.app.service('user-collections');
  if (!hook.params.user) return Promise.resolve({});

  return svcUserCollections.find({
    query: {
      user: hook.params.user.id,
      subject: { $in: fp.map(fp.prop('id'), docs) },
      $select: 'collect,*'
    },
    paginate: false
  }).then(results => {
    results = results? results.data || results : [];
    const documents = fp.groupBy(fp.prop('id'), results);
    return fp.reduce((acc, doc) => {
      if (documents[doc.id] && documents[doc.id].length > 0) {
        acc[doc.id] = documents[doc.id][0].collect;
      }
      return acc;
    }, {}, docs);
  });
}

function getFavorites (hook, docs, options) {
  const svcUserFavorites = hook.app.service('user-favorites');
  if (!hook.params.user) return Promise.resolve();

  return svcUserFavorites.find({
    query: {
      user: hook.params.user.id,
      document: { $in: fp.map(fp.prop('id'), docs) },
    },
    paginate: false
  }).then((results) => {
    results = results? results.data || results : [];
    const documents = fp.groupBy(fp.prop('id'));
    return fp.reduce((acc, doc) => {
      acc[doc.id] = { isFavorite: documents[doc.id] && documents[doc.id].length > 0 || false };
      return acc;
    }, {}, docs);
  });
}

function getAcls (hook, docs, options) {
  const inheritedDocs = fp.filter(fp.propEq('inherited', true), docs);
  return Promise.all([
    getAces(hook.app, docs),
    getParentAces(hook.app, inheritedDocs)
  ]).then(([localAces, inheritedAces]) => {
    const aces = fp.reduce((acc, doc) => {
      acc[doc.id] = acc[doc.id] || [];
      if (localAces && localAces[doc.id] && localAces[doc.id].length > 0) {
        acc[doc.id].push({ name: 'local', aces: localAces[doc.id] });
      }
      return acc;
    }, {}, docs);
    return fp.reduce((acc, doc) => {
      acc[doc.id] = acc[doc.id] || [];
      if (inheritedAces && inheritedAces[doc.id] && inheritedAces[doc.id].length > 0) {
        acc[doc.id].push({ name: 'inherited', aces: inheritedAces[doc.id] });
      }
      return acc;
    }, aces, docs);
  });
}

function getPermission (hook, docs, options) {
  const Types = options.DocTypes || DocTypes;
  return Promise.resolve(fp.reduce((acc, doc) => {
    const subtypes = Types[doc.type] && Types[doc.type].subtypes;
    // TODO: user acls
    let permissions = ['Everything', 'Read', 'Write', 'ReadWrite'];
    if (subtypes) {
      permissions = permissions.concat(['ReadChildren', 'AddChildren', 'RemoveChildren']);
    }
    acc[doc.id] = permissions;
    return acc;
  }, {}, docs));
}

function getUserVisiblePermissions (hook, docs, options) {
  return Promise.resolve(fp.reduce((acc, doc) => {
    acc[doc.id] = ['Read', 'ReadWrite', 'Everything'];
    return acc;
  }, {}, docs));
}

function getSubtypes (hook, docs, options) {
  const Types = options.DocTypes || DocTypes;
  return Promise.resolve(fp.reduce((acc, doc) => {
    const subtypes = Types[doc.type] && Types[doc.type].subtypes;
    acc[doc.id] = subtypes? Object.values(fp.pick(subtypes, Types)) : [];
    return acc;
  }, {}, docs));
}

function getTags (hook, docs, options) {
  return Promise.resolve(fp.reduce((acc, doc) => {
    acc[doc.id] = doc.tags || [];
    return acc;
  }, {}, docs));
}

function getThumbnail (hook, docs) {
  const baseUrl = 'bower_components/playing-content-elements/images/icons/';
  return Promise.resolve(fp.reduce((acc, doc) => {
    acc[doc.id] = {
      url: url.resolve(baseUrl, doc.type + '.png')
    };
    return acc;
  }, {}, docs));
}

// Add document metadata according to request header
export default function documentEnrichers (options = {}) {
  return (hook) => {
    assert(hook.type === 'after', `documentEnrichers must be used as a 'after' hook.`);

    const documentEnrichers = hook.params &&
      (hook.params.headers && hook.params.headers['enrichers-document']);

      // If no enrichers-document header then skip this hook
    if (!documentEnrichers) {
      debug('Skip documentEnrichers without headers', hook.params.query);
      return hook;
    }

    const enrichers = fp.map(fp.trim, fp.split(',', documentEnrichers));
    debug('enrichers-document %j', enrichers);

    let documents = helpers.getHookDataAsArray(hook);
    let promises = {};

    enrichers.forEach(enricher => {
      switch(enricher) {
        case 'acls':
          promises.acls = getAcls(hook, documents, options);
          break;
        case 'breadcrumbs':
          promises.breadcrumbs = getBreadcrumbs(hook, documents, options);
          break;
        case 'collections':
          promises.collections = getCollections(hook, documents, options);
          break;
        case 'favorites':
          promises.favorites = getFavorites(hook, documents, options);
          break;
        case 'hasFolderishChild':
          promises.hasFolderishChild = hasFolderishChild(hook, documents, options);
          break;
        case 'permissions':
          promises.permissions = getPermission(hook, documents, options);
          break;
        case 'preview':
          promises.preview = Promise.resolve(null);
          break;
        case 'renditions':
          promises.renditions = Promise.resolve(null);
          break;
        case 'subtypes':
          promises.subtypes = getSubtypes(hook, documents, options);
          break;
        case 'tags':
          promises.tags = getTags(hook, documents, options);
          break;
        case 'thumbnail':
          promises.thumbnail = getThumbnail(hook, documents, options);
          break;
        case 'userVisiblePermissions':
          promises.userVisiblePermissions = getUserVisiblePermissions(hook, documents, options);
          break;
      }
    });

    return Promise.props(promises).then(results => {
      documents.forEach(doc => {
        doc.metadata = doc.metadata || {};
        for (const enricher of Object.keys(results)) {
          if (results[enricher]) {
            doc.metadata[enricher] = results[enricher][doc.id];
          }
        }
        doc.metadata = fp.sortKeys(doc.metadata);
      });
      return hook;
    });
  };
}
<|endoftext|>


<commit_before>/*
 * mobile listview unit tests
 */

// TODO split out into seperate test files
(function($){
	module('Basic Linked list', {
		setup: function(){
			$.testHelper.openPage("#basic-linked-test");
		}
	});

	asyncTest( "The page should enhanced correctly", function(){
		setTimeout(function() {
			ok($('#basic-linked-test [role="option"]').length == 3, "roles added to li elements");
			start();
		}, 100);
	});

	asyncTest( "Slides to the listview page when the li a is clicked", function() {
		$.testHelper.openPage("#basic-linked-test");

		setTimeout(function(){
			$('#basic-linked-test li a').first().click();
		}, 500);

		setTimeout(function() {
			ok($('#basic-link-results').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	asyncTest( "Slides back to main page when back button is clicked", function() {
		$.testHelper.openPage("#basic-link-results");

		$('#basic-link-results a:contains("Back")').click();
		setTimeout(function() {
			ok($('#basic-linked-test').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module('Nested List Test');

	asyncTest( "Changes page to nested list test and enhances", function() {
		location.href = location.href.split('#')[0] + "#nested-list-test";
		setTimeout(function() {
			ok($('#nested-list-test').hasClass('ui-page-active'), "makes nested list test page active");
			ok($('[role="option"]', $('#nested-list-test')).length == 2, 'Adds data role to the two LIs');
			ok($('body > :jqmData(url="nested-list-test&ui-page=More-animals-0")').length == 1, "Adds first UL to the page");
			ok($('body > :jqmData(url="nested-list-test&ui-page=Groups-of-animals-1")').length == 1, "Adds second nested UL to the page");
			start();
		}, 1000);
	});

	asyncTest( "change to nested page when the li is clicked", function() {
		$('.ui-page-active li:eq(1)').click();
				setTimeout(function() {
					var $new_page = $('body > :jqmData(url="nested-list-test&ui-page=More-animals-0")');
					ok($new_page.hasClass('ui-page-active'), 'Makes the nested page the active page.');
					ok($('.ui-listview', $new_page).find(":contains('Rhumba of rattlesnakes')").length == 1, "The current page should have the proper text in the list.");
					ok($('.ui-listview', $new_page).find(":contains('Shoal of Bass')").length == 1, "The current page should have the proper text in the list.");
					start();
				}, 1000);
	});

	asyncTest( "should go back to top level when the back button is clicked", function() {
		$('body > :jqmData(url="nested-list-test&ui-page=More-animals-0")').find('a:contains("Back")').click();
		setTimeout(function() {
			ok($('#nested-list-test').hasClass('ui-page-active'), 'Transitions back to the parent nested page');
			start();
		}, 1000);
	});

	test( "nested list title should use first text node, regardless of line breaks", function(){
		ok($('#nested-list-test .linebreaknode').text() === "More animals", 'Text should be "More animals"');
	});

	module('Ordered Lists');

	asyncTest( "changes to the numbered list page and enhances it", function() {
		location.href = location.href.split('#')[0] + "#numbered-list-test";
		setTimeout(function() {
			var $new_page = $('#numbered-list-test');
			ok($new_page.hasClass('ui-page-active'), "Makes the new page active when the hash is changed.");
			ok($('[role="option"]', $new_page).length == 3, "There should be three LI that are enhanced");
			ok($('.ui-link-inherit', $new_page).first().text() == "Number 1", "The text of the first LI should be Number 1");
			start();
		}, 1000);
	});

	asyncTest( "changes to number 1 page when the li is clicked", function() {
		$('.ui-page-active li').first().click();
		setTimeout(function() {
			ok($('#numbered-list-results').hasClass('ui-page-active'), "The new numbered page was transitioned correctly.");
			start();
		}, 1000);
	});

	asyncTest( "takes us back to the numbered list when the back button is clicked", function() {
		$('.ui-page-active a:contains("Back")').click();
		setTimeout(function() {
			ok($('#numbered-list-test').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module('Read only list');

	asyncTest( "changes to the read only page when hash is changed", function() {
		location.href = location.href.split('#')[0] + "#read-only-list-test";
		setTimeout(function() {
			var $new_page = $('#read-only-list-test');
			ok($new_page.hasClass('ui-page-active'), "makes the read only page the active page");
			ok($('[role="option"]', $new_page).length === 4, "There are 4 li that enhanced as role options");
			ok($('li', $new_page).first().text() === "Read", "The first LI has the proper text.");
			start();
		}, 1000);
	});

	asyncTest( "Does not go to new page when an item is clicked", function() {
		$('li', $('#read-only-list-test').first().click());
		setTimeout(function() {
			ok($('.ui-page-active').attr('id') == "read-only-list-test", "Page does not change for read only lists");
			start();
		}, 1000);
	});

	module('Split view list');

	asyncTest( "changes the page to the split view list and enhances it correctly.", function() {
		$.testHelper.openPage("#split-list-test");

		setTimeout(function() {
			var $new_page = $('#split-list-test');
			ok($('[role="option"]', $new_page).length == 3);
			ok($('.ui-li-link-alt', $new_page).length == 3);
			ok($('.ui-link-inherit', $new_page).length == 3);
			start();
		}, 1000);
	});

	asyncTest( "change the page to the split view page 1 when the first link is clicked", function() {
		$.testHelper.openPage("#split-list-test");

		setTimeout(function(){
			$('.ui-page-active [role="option"]:eq(0)').click();
		}, 500);

		setTimeout(function() {
			ok($('#split-list-link1').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	asyncTest( "Slide back to the parent list view when the back button is clicked", function() {
		$.testHelper.openPage("#split-list-test");

		$.testHelper.sequence([
			function(){
				$('.ui-page-active [role="option"]:eq(0)').click();
			},

			function(){
				$('.ui-page-active a:contains("Back")').click();
			},

			function() {
				ok($('#split-list-test').hasClass('ui-page-active'));
				start();
			}
		], 1000);
	});

	asyncTest( "Clicking on the icon (the second link) should take the user to other a href of this LI", function() {
		$.testHelper.openPage("#split-list-test");

		setTimeout(function(){
			$('.ui-page-active .ui-li-link-alt:eq(0)').click();
		}, 500);

		setTimeout(function() {
			ok($('#split-list-link2').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module( "List Dividers" );

	asyncTest( "Makes the list divider page the active page and enhances it correctly.", function() {
		location.href = location.href.split('#')[0] + "#list-divider-test";
		setTimeout(function() {
			var $new_page = $('#list-divider-test');
			ok($new_page.find('.ui-li-divider').length == 2);
			ok($new_page.hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module( "Search Filter");

	var searchFilterId = "#search-filter-test";

	asyncTest( "Filter downs results when the user enters information", function() {
		var $searchPage = $(searchFilterId);
		$.testHelper.openPage(searchFilterId);

		setTimeout(function(){
			$searchPage.find('input').val('at');
			$searchPage.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($searchPage.find('li[style^="display: none;"]').length, 2);
			start();
		}, 1000);
	});

	asyncTest( "Redisplay results when user removes values", function() {
		var $searchPage = $(searchFilterId);
		$.testHelper.openPage(searchFilterId);

		setTimeout(function(){
			$searchPage.find('input').val('a');
			$searchPage.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($searchPage.find("li[style^='display: none;']").length, 0);
			start();
		}, 1000);
	});

	test( "Refresh applys thumb styling", function(){
		var ul = $('.ui-page-active ul');

		ul.append("

");
		ok(!ul.find("#fiz img").hasClass("ui-li-thumb"));
		ul.listview('refresh');
		ok(ul.find("#fiz img").hasClass("ui-li-thumb"));
	});

	asyncTest( "Filter downs results and dividers when the user enters information", function() {
		var	$searchPage = $("#search-filter-with-dividers-test");
		$.testHelper.openPage("#search-filter-with-dividers-test");

		// wait for the page to become active/enhanced
		setTimeout(function(){
			$searchPage.find('input').val('at');
			$searchPage.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			//there should be four hidden list entries
			same($searchPage.find('li[style^="display: none;"]').length, 4);

			//there should be two list entries that are list dividers and hidden
			same($searchPage.find('li:jqmData(role=list-divider)[style^="display: none;"]').length, 2);

			//there should be two list entries that are not list dividers and hidden
			same($searchPage.find('li:not(:jqmData(role=list-divider))[style^="display: none;"]').length, 2);
			start();
		}, 1000);
	});

	asyncTest( "Redisplay results when user removes values", function() {
		$.testHelper.openPage("#search-filter-with-dividers-test");

		// wait for the page to become active/enhanced
		setTimeout(function(){
			$('.ui-page-active input').val('a');
			$('.ui-page-active input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($('.ui-page-active input').val(), 'a');
			same($('.ui-page-active li[style^="display: none;"]').length, 0);
			start();
		}, 1000);
	});

	asyncTest( "Dividers are hidden when preceding hidden rows and shown when preceding shown rows", function () {
		$.testHelper.openPage("#search-filter-with-dividers-test");
		var $page = $('.ui-page-active');

		// wait for the page to become active/enhanced
		setTimeout(function(){
			$page.find('input').val('at');
			$page.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($page.find('li:jqmData(role=list-divider)[style^="display: none;"]').length, 2);
			same($page.find('li:jqmData(role=list-divider)[style^="display: none;"] + li:not(:jqmData(role=list-divider))[style^="display: none;"]').length, 2);
			same($page.find('li:jqmData(role=list-divider):not([style^="display: none;"]) + li:not(:jqmData(role=list-divider)):not([style^="display: none;"])').length, 2);
			start();
		}, 1000);
	});
})(jQuery);<commit_msg>used data-rel to find back buttons<commit_after>/*
 * mobile listview unit tests
 */

// TODO split out into seperate test files
(function($){
	module('Basic Linked list', {
		setup: function(){
			$.testHelper.openPage("#basic-linked-test");
		}
	});

	asyncTest( "The page should enhanced correctly", function(){
		setTimeout(function() {
			ok($('#basic-linked-test [role="option"]').length == 3, "roles added to li elements");
			start();
		}, 100);
	});

	asyncTest( "Slides to the listview page when the li a is clicked", function() {
		$.testHelper.openPage("#basic-linked-test");

		setTimeout(function(){
			$('#basic-linked-test li a').first().click();
		}, 500);

		setTimeout(function() {
			ok($('#basic-link-results').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	asyncTest( "Slides back to main page when back button is clicked", function() {
		$.testHelper.openPage("#basic-link-results");

		$('#basic-link-results a:jqmData(rel="Back")').click();
		setTimeout(function() {
			ok($('#basic-linked-test').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module('Nested List Test');

	asyncTest( "Changes page to nested list test and enhances", function() {
		location.href = location.href.split('#')[0] + "#nested-list-test";
		setTimeout(function() {
			ok($('#nested-list-test').hasClass('ui-page-active'), "makes nested list test page active");
			ok($('[role="option"]', $('#nested-list-test')).length == 2, 'Adds data role to the two LIs');
			ok($('body > :jqmData(url="nested-list-test&ui-page=More-animals-0")').length == 1, "Adds first UL to the page");
			ok($('body > :jqmData(url="nested-list-test&ui-page=Groups-of-animals-1")').length == 1, "Adds second nested UL to the page");
			start();
		}, 1000);
	});

	asyncTest( "change to nested page when the li is clicked", function() {
		$('.ui-page-active li:eq(1)').click();
				setTimeout(function() {
					var $new_page = $('body > :jqmData(url="nested-list-test&ui-page=More-animals-0")');
					ok($new_page.hasClass('ui-page-active'), 'Makes the nested page the active page.');
					ok($('.ui-listview', $new_page).find(":contains('Rhumba of rattlesnakes')").length == 1, "The current page should have the proper text in the list.");
					ok($('.ui-listview', $new_page).find(":contains('Shoal of Bass')").length == 1, "The current page should have the proper text in the list.");
					start();
				}, 1000);
	});

	asyncTest( "should go back to top level when the back button is clicked", function() {
		$('body > :jqmData(url="nested-list-test&ui-page=More-animals-0")').find('a:jqmData(rel="Back")').click();
		setTimeout(function() {
			ok($('#nested-list-test').hasClass('ui-page-active'), 'Transitions back to the parent nested page');
			start();
		}, 1000);
	});

	test( "nested list title should use first text node, regardless of line breaks", function(){
		ok($('#nested-list-test .linebreaknode').text() === "More animals", 'Text should be "More animals"');
	});

	module('Ordered Lists');

	asyncTest( "changes to the numbered list page and enhances it", function() {
		location.href = location.href.split('#')[0] + "#numbered-list-test";
		setTimeout(function() {
			var $new_page = $('#numbered-list-test');
			ok($new_page.hasClass('ui-page-active'), "Makes the new page active when the hash is changed.");
			ok($('[role="option"]', $new_page).length == 3, "There should be three LI that are enhanced");
			ok($('.ui-link-inherit', $new_page).first().text() == "Number 1", "The text of the first LI should be Number 1");
			start();
		}, 1000);
	});

	asyncTest( "changes to number 1 page when the li is clicked", function() {
		$('.ui-page-active li').first().click();
		setTimeout(function() {
			ok($('#numbered-list-results').hasClass('ui-page-active'), "The new numbered page was transitioned correctly.");
			start();
		}, 1000);
	});

	asyncTest( "takes us back to the numbered list when the back button is clicked", function() {
		$('.ui-page-active a:jqmData(rel="Back")').click();
		setTimeout(function() {
			ok($('#numbered-list-test').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module('Read only list');

	asyncTest( "changes to the read only page when hash is changed", function() {
		location.href = location.href.split('#')[0] + "#read-only-list-test";
		setTimeout(function() {
			var $new_page = $('#read-only-list-test');
			ok($new_page.hasClass('ui-page-active'), "makes the read only page the active page");
			ok($('[role="option"]', $new_page).length === 4, "There are 4 li that enhanced as role options");
			ok($('li', $new_page).first().text() === "Read", "The first LI has the proper text.");
			start();
		}, 1000);
	});

	asyncTest( "Does not go to new page when an item is clicked", function() {
		$('li', $('#read-only-list-test').first().click());
		setTimeout(function() {
			ok($('.ui-page-active').attr('id') == "read-only-list-test", "Page does not change for read only lists");
			start();
		}, 1000);
	});

	module('Split view list');

	asyncTest( "changes the page to the split view list and enhances it correctly.", function() {
		$.testHelper.openPage("#split-list-test");

		setTimeout(function() {
			var $new_page = $('#split-list-test');
			ok($('[role="option"]', $new_page).length == 3);
			ok($('.ui-li-link-alt', $new_page).length == 3);
			ok($('.ui-link-inherit', $new_page).length == 3);
			start();
		}, 1000);
	});

	asyncTest( "change the page to the split view page 1 when the first link is clicked", function() {
		$.testHelper.openPage("#split-list-test");

		setTimeout(function(){
			$('.ui-page-active [role="option"]:eq(0)').click();
		}, 500);

		setTimeout(function() {
			ok($('#split-list-link1').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	asyncTest( "Slide back to the parent list view when the back button is clicked", function() {
		$.testHelper.openPage("#split-list-test");

		$.testHelper.sequence([
			function(){
				$('.ui-page-active [role="option"]:eq(0)').click();
			},

			function(){
				$('.ui-page-active a:jqmData(rel="Back")').click();
			},

			function() {
				ok($('#split-list-test').hasClass('ui-page-active'));
				start();
			}
		], 1000);
	});

	asyncTest( "Clicking on the icon (the second link) should take the user to other a href of this LI", function() {
		$.testHelper.openPage("#split-list-test");

		setTimeout(function(){
			$('.ui-page-active .ui-li-link-alt:eq(0)').click();
		}, 500);

		setTimeout(function() {
			ok($('#split-list-link2').hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module( "List Dividers" );

	asyncTest( "Makes the list divider page the active page and enhances it correctly.", function() {
		location.href = location.href.split('#')[0] + "#list-divider-test";
		setTimeout(function() {
			var $new_page = $('#list-divider-test');
			ok($new_page.find('.ui-li-divider').length == 2);
			ok($new_page.hasClass('ui-page-active'));
			start();
		}, 1000);
	});

	module( "Search Filter");

	var searchFilterId = "#search-filter-test";

	asyncTest( "Filter downs results when the user enters information", function() {
		var $searchPage = $(searchFilterId);
		$.testHelper.openPage(searchFilterId);

		setTimeout(function(){
			$searchPage.find('input').val('at');
			$searchPage.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($searchPage.find('li[style^="display: none;"]').length, 2);
			start();
		}, 1000);
	});

	asyncTest( "Redisplay results when user removes values", function() {
		var $searchPage = $(searchFilterId);
		$.testHelper.openPage(searchFilterId);

		setTimeout(function(){
			$searchPage.find('input').val('a');
			$searchPage.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($searchPage.find("li[style^='display: none;']").length, 0);
			start();
		}, 1000);
	});

	test( "Refresh applys thumb styling", function(){
		var ul = $('.ui-page-active ul');

		ul.append("

");
		ok(!ul.find("#fiz img").hasClass("ui-li-thumb"));
		ul.listview('refresh');
		ok(ul.find("#fiz img").hasClass("ui-li-thumb"));
	});

	asyncTest( "Filter downs results and dividers when the user enters information", function() {
		var	$searchPage = $("#search-filter-with-dividers-test");
		$.testHelper.openPage("#search-filter-with-dividers-test");

		// wait for the page to become active/enhanced
		setTimeout(function(){
			$searchPage.find('input').val('at');
			$searchPage.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			//there should be four hidden list entries
			same($searchPage.find('li[style^="display: none;"]').length, 4);

			//there should be two list entries that are list dividers and hidden
			same($searchPage.find('li:jqmData(role=list-divider)[style^="display: none;"]').length, 2);

			//there should be two list entries that are not list dividers and hidden
			same($searchPage.find('li:not(:jqmData(role=list-divider))[style^="display: none;"]').length, 2);
			start();
		}, 1000);
	});

	asyncTest( "Redisplay results when user removes values", function() {
		$.testHelper.openPage("#search-filter-with-dividers-test");

		// wait for the page to become active/enhanced
		setTimeout(function(){
			$('.ui-page-active input').val('a');
			$('.ui-page-active input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($('.ui-page-active input').val(), 'a');
			same($('.ui-page-active li[style^="display: none;"]').length, 0);
			start();
		}, 1000);
	});

	asyncTest( "Dividers are hidden when preceding hidden rows and shown when preceding shown rows", function () {
		$.testHelper.openPage("#search-filter-with-dividers-test");
		var $page = $('.ui-page-active');

		// wait for the page to become active/enhanced
		setTimeout(function(){
			$page.find('input').val('at');
			$page.find('input').trigger('change');
		}, 500);

		setTimeout(function() {
			same($page.find('li:jqmData(role=list-divider)[style^="display: none;"]').length, 2);
			same($page.find('li:jqmData(role=list-divider)[style^="display: none;"] + li:not(:jqmData(role=list-divider))[style^="display: none;"]').length, 2);
			same($page.find('li:jqmData(role=list-divider):not([style^="display: none;"]) + li:not(:jqmData(role=list-divider)):not([style^="display: none;"])').length, 2);
			start();
		}, 1000);
	});
})(jQuery);<|endoftext|>


<commit_before>import SegmentTrieNode from 'ember-route-recognizer/-private/segment-trie/segment-trie-node';
import DynamicSegment from 'ember-route-recognizer/-private/segment-trie/dynamic-segment';
import EpsilonSegment from 'ember-route-recognizer/-private/segment-trie/epsilon-segment';
import GlobNode from 'ember-route-recognizer/-private/segment-trie/glob-node';
import StaticSegment from 'ember-route-recognizer/-private/segment-trie/static-segment';

import { module, test } from 'qunit';

module('Unit | SegmentTrieNode');

test('SegmentTrieNode#equivalent', function(assert) {
  let one = new StaticSegment('value');
  let two = new StaticSegment('value');

  assert.ok(one.equivalent(two), "Identically constructed nodes are equivalent.");

  two.handler = "something";
  assert.ok(!one.equivalent(two), "Modifying the node makes them non-equivalent.");
});

test('StaticSegment#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new StaticSegment({}, 'child');
  let child2 = new StaticSegment({}, 'child');
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.staticSegments['child'];
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('StaticSegment#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new StaticSegment({}, 'child');
  let child2 = new StaticSegment({}, 'child');
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.staticSegments['child'];
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('EpsilonSegment#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new EpsilonSegment({});
  let child2 = new EpsilonSegment({});
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.epsilonSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('EpsilonSegment#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new EpsilonSegment({});
  let child2 = new EpsilonSegment({});
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.epsilonSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('DynamicSegment#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new DynamicSegment({}, 'child');
  let child2 = new DynamicSegment({}, 'child');
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.dynamicSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('DynamicSegment#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new DynamicSegment({}, 'child');
  let child2 = new DynamicSegment({}, 'child');
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.dynamicSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('new GlobNode()', function(assert) {
  let globNode = new GlobNode({}, 'globby');
  assert.ok(globNode.children.globNodes[0] === globNode, "Maintains a circular reference to itself.");
});

test('GlobNode#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new GlobNode({}, 'child');
  let child2 = new GlobNode({}, 'child');
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.globNodes;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('GlobNode#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new GlobNode({}, 'child');
  let child2 = new GlobNode({}, 'child');
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.globNodes;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});
<commit_msg>Fix bugs in tests.<commit_after>import DynamicSegment from 'ember-route-recognizer/-private/segment-trie/dynamic-segment';
import EpsilonSegment from 'ember-route-recognizer/-private/segment-trie/epsilon-segment';
import GlobNode from 'ember-route-recognizer/-private/segment-trie/glob-node';
import StaticSegment from 'ember-route-recognizer/-private/segment-trie/static-segment';

import { module, test } from 'qunit';

module('Unit | SegmentTrieNode');

test('SegmentTrieNode#_equivalent', function(assert) {
  let one = new StaticSegment({}, 'value');
  let two = new StaticSegment({}, 'value');

  assert.ok(one._equivalent(two), "Identically constructed nodes are equivalent.");

  two.handler = "something";
  assert.ok(!one._equivalent(two), "Modifying the node makes them non-equivalent.");
});

test('StaticSegment#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new StaticSegment({}, 'child');
  let child2 = new StaticSegment({}, 'child');
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.staticSegments['child'];
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('StaticSegment#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new StaticSegment({}, 'child');
  let child2 = new StaticSegment({}, 'child');
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.staticSegments['child'];
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('EpsilonSegment#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new EpsilonSegment({});
  let child2 = new EpsilonSegment({});
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.epsilonSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('EpsilonSegment#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new EpsilonSegment({});
  let child2 = new EpsilonSegment({});
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.epsilonSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('DynamicSegment#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new DynamicSegment({}, 'child');
  let child2 = new DynamicSegment({}, 'child');
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.dynamicSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('DynamicSegment#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new DynamicSegment({}, 'child');
  let child2 = new DynamicSegment({}, 'child');
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.dynamicSegments;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('new GlobNode()', function(assert) {
  let globNode = new GlobNode({}, 'globby');
  assert.ok(globNode.children.globNodes[0] === globNode, "Maintains a circular reference to itself.");
});

test('GlobNode#append', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new GlobNode({}, 'child');
  let child2 = new GlobNode({}, 'child');
  let append1 = parent.append(child1);
  let append2 = parent.append(child2);

  let haystack = parent.children.globNodes;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});

test('GlobNode#appendTo', function(assert) {
  let parent = new StaticSegment({}, 'parent');
  let child1 = new GlobNode({}, 'child');
  let child2 = new GlobNode({}, 'child');
  let append1 = child1.appendTo(parent);
  let append2 = child2.appendTo(parent);

  let haystack = parent.children.globNodes;
  assert.ok(haystack.indexOf(child1) !== -1, "Appends the first child.");
  assert.ok(haystack.indexOf(child2) === -1, "Collapses an equivalent trie node on insertion.");
  assert.ok(append1 === child1, "Return value matches the appended node on addition.");
  assert.ok(append2 === child1, "Return value matches the equivalent node on collapsing.");
});
<|endoftext|>


<commit_before>import * as React from "react";
import ReactAutocomplete from "react-autocomplete";
import PropTypes from "prop-types";
import { compose } from "recompose";
import formInput, { formInputProps } from "@crave/farmblocks-hoc-input";
import {
  DropdownMenuWrapper,
  DropdownItemWrapper
} from "@crave/farmblocks-dropdown";
import withMessages, {
  withMessagesProps
} from "@crave/farmblocks-hoc-validation-messages";

import withImage, { refName } from "./components/withImage";
import Item from "./components/Item";
import EmptyCard from "./components/EmptyCard";
import DropdownWrapper from "./styledComponents/DropdownWrapper";

const EnhancedInput = compose(
  withMessages,
  formInput,
  withImage
)("input");
EnhancedInput.displayName = "EnhancedInput";

class Select extends React.Component {
  render() {
    const { width, zIndex, items } = this.props;
    return (
      <DropdownWrapper width={width} zIndex={zIndex}>
        <ReactAutocomplete
          items={items}
          shouldItemRender={this._shouldItemRender}
          getItemValue={item => item.label}
          value={this.state.selectedLabel}
          onChange={this.onFilter}
          onSelect={this.onSelect}
          renderInput={this._renderInput}
          renderMenu={this._renderMenu}
          renderItem={this._renderItem}
          autoHighlight={false}
          onMenuVisibilityChange={this.onMenuVisibilityChange}
          wrapperStyle={{}}
          ref={ref => (this.input = ref)}
        />
      </DropdownWrapper>
    );
  }

  onMenuVisibilityChange = isMenuOpen => {
    if (!this.props.disableSearch && isMenuOpen && this.input) {
      this.input.select();
    }
    this.setState({ isMenuOpen });
  };

  onFilter = event => {
    !this.state.isSearching && this.props.onChange("");

    this.setState({ selectedLabel: event.target.value, isSearching: true });
  };

  onSelect = (selectedLabel, item) => {
    this.setState({ selectedLabel, isSearching: false });
    this.props.onChange(item.value);
  };

  getSelectedLabel = props => {
    const item =
      (props.value || props.value === 0) &&
      props.items.find(x => x.value === props.value);

    if (item) {
      return item.label;
    }
  };

  componentDidUpdate = prevProps => {
    const { value } = this.props;
    if (
      ((value || value === 0) &&
        value !== prevProps.value &&
        value !== this.state.selectedValue) ||
      prevProps.items !== this.props.items
    ) {
      this.setState({
        selectedValue: value,
        selectedLabel: this.getSelectedLabel(this.props)
      });
    }
  };

  _renderInput = autoCompleteProps => {
    const { ref, ...rest } = autoCompleteProps;

    const {
      renderItem,
      disableSearch,
      items,
      zIndex,
      maxHeight,
      ...inputProps
    } = this.props;

    inputProps.validationMessages = this.state.isMenuOpen
      ? []
      : this.props.validationMessages;

    const selectedItem = items.find(
      item => item.label === autoCompleteProps.value
    );
    const image = selectedItem && selectedItem.image;
    return (
      <EnhancedInput
        readOnly={disableSearch}
        {...inputProps}
        {...rest}
        innerRef={ref}
        refName={refName}
        image={image}
      />
    );
  };

  _renderMenu = items => {
    const { noResultsMessage, maxHeight } = this.props;

    if (!items || !items.length) {
      return <EmptyCard noResultsMessage={noResultsMessage} />;
    }
    return (
      <DropdownMenuWrapper maxHeight={maxHeight}>

{items}

      </DropdownMenuWrapper>
    );
  };

  _renderItem = (item, highlighted) => {
    const selected = this.state.selectedLabel === item.label;
    return (
      <DropdownItemWrapper
        key={item.value}
        highlighted={highlighted}
        selected={selected}
      >
        {this.props.renderItem ? (
          this.props.renderItem(item, selected)
        ) : (
          <Item
            label={item.label}
            id={this.props.id && `${this.props.id}-item-${item.label}`}
            image={item.image}
            selected={selected}
          />
        )}
      </DropdownItemWrapper>
    );
  };

  _shouldItemRender = item => {
    const { selectedLabel } = this.state;
    if (this.state.isSearching) {
      return (
        item.label
          .toLowerCase()
          .indexOf(selectedLabel && selectedLabel.toLowerCase()) > -1
      );
    }

    // If user is not searching, we render the full list of items
    return true;
  };

  state = {
    selectedValue: this.props.value,
    selectedLabel: this.getSelectedLabel(this.props),
    isSearching: false,
    isMenuOpen: false
  };

  static defaultProps = {
    onChange: () => false,
    width: "200px",
    items: []
  };

  static propTypes = {
    items: PropTypes.arrayOf(
      PropTypes.shape({
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        label: PropTypes.string,
        image: PropTypes.string
      })
    ).isRequired,
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    width: PropTypes.string,
    onChange: PropTypes.func,
    renderItem: PropTypes.func,
    noResultsMessage: PropTypes.string,
    disableSearch: PropTypes.bool,
    zIndex: PropTypes.number,
    maxHeight: PropTypes.string,
    ...formInputProps,
    ...withMessagesProps
  };
}

export default Select;
<commit_msg>feat(Select): Use value to compose Item id<commit_after>import * as React from "react";
import ReactAutocomplete from "react-autocomplete";
import PropTypes from "prop-types";
import { compose } from "recompose";
import formInput, { formInputProps } from "@crave/farmblocks-hoc-input";
import {
  DropdownMenuWrapper,
  DropdownItemWrapper
} from "@crave/farmblocks-dropdown";
import withMessages, {
  withMessagesProps
} from "@crave/farmblocks-hoc-validation-messages";

import withImage, { refName } from "./components/withImage";
import Item from "./components/Item";
import EmptyCard from "./components/EmptyCard";
import DropdownWrapper from "./styledComponents/DropdownWrapper";

const EnhancedInput = compose(
  withMessages,
  formInput,
  withImage
)("input");
EnhancedInput.displayName = "EnhancedInput";

class Select extends React.Component {
  render() {
    const { width, zIndex, items } = this.props;
    return (
      <DropdownWrapper width={width} zIndex={zIndex}>
        <ReactAutocomplete
          items={items}
          shouldItemRender={this._shouldItemRender}
          getItemValue={item => item.label}
          value={this.state.selectedLabel}
          onChange={this.onFilter}
          onSelect={this.onSelect}
          renderInput={this._renderInput}
          renderMenu={this._renderMenu}
          renderItem={this._renderItem}
          autoHighlight={false}
          onMenuVisibilityChange={this.onMenuVisibilityChange}
          wrapperStyle={{}}
          ref={ref => (this.input = ref)}
        />
      </DropdownWrapper>
    );
  }

  onMenuVisibilityChange = isMenuOpen => {
    if (!this.props.disableSearch && isMenuOpen && this.input) {
      this.input.select();
    }
    this.setState({ isMenuOpen });
  };

  onFilter = event => {
    !this.state.isSearching && this.props.onChange("");

    this.setState({ selectedLabel: event.target.value, isSearching: true });
  };

  onSelect = (selectedLabel, item) => {
    this.setState({ selectedLabel, isSearching: false });
    this.props.onChange(item.value);
  };

  getSelectedLabel = props => {
    const item =
      (props.value || props.value === 0) &&
      props.items.find(x => x.value === props.value);

    if (item) {
      return item.label;
    }
  };

  componentDidUpdate = prevProps => {
    const { value } = this.props;
    if (
      ((value || value === 0) &&
        value !== prevProps.value &&
        value !== this.state.selectedValue) ||
      prevProps.items !== this.props.items
    ) {
      this.setState({
        selectedValue: value,
        selectedLabel: this.getSelectedLabel(this.props)
      });
    }
  };

  _renderInput = autoCompleteProps => {
    const { ref, ...rest } = autoCompleteProps;

    const {
      renderItem,
      disableSearch,
      items,
      zIndex,
      maxHeight,
      ...inputProps
    } = this.props;

    inputProps.validationMessages = this.state.isMenuOpen
      ? []
      : this.props.validationMessages;

    const selectedItem = items.find(
      item => item.label === autoCompleteProps.value
    );
    const image = selectedItem && selectedItem.image;
    return (
      <EnhancedInput
        readOnly={disableSearch}
        {...inputProps}
        {...rest}
        innerRef={ref}
        refName={refName}
        image={image}
      />
    );
  };

  _renderMenu = items => {
    const { noResultsMessage, maxHeight } = this.props;

    if (!items || !items.length) {
      return <EmptyCard noResultsMessage={noResultsMessage} />;
    }
    return (
      <DropdownMenuWrapper maxHeight={maxHeight}>

{items}

      </DropdownMenuWrapper>
    );
  };

  _renderItem = (item, highlighted) => {
    const selected = this.state.selectedLabel === item.label;
    return (
      <DropdownItemWrapper
        key={item.value}
        highlighted={highlighted}
        selected={selected}
      >
        {this.props.renderItem ? (
          this.props.renderItem(item, selected)
        ) : (
          <Item
            label={item.label}
            id={this.props.id && `${this.props.id}-item-${item.value}`}
            image={item.image}
            selected={selected}
          />
        )}
      </DropdownItemWrapper>
    );
  };

  _shouldItemRender = item => {
    const { selectedLabel } = this.state;
    if (this.state.isSearching) {
      return (
        item.label
          .toLowerCase()
          .indexOf(selectedLabel && selectedLabel.toLowerCase()) > -1
      );
    }

    // If user is not searching, we render the full list of items
    return true;
  };

  state = {
    selectedValue: this.props.value,
    selectedLabel: this.getSelectedLabel(this.props),
    isSearching: false,
    isMenuOpen: false
  };

  static defaultProps = {
    onChange: () => false,
    width: "200px",
    items: []
  };

  static propTypes = {
    items: PropTypes.arrayOf(
      PropTypes.shape({
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        label: PropTypes.string,
        image: PropTypes.string
      })
    ).isRequired,
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    width: PropTypes.string,
    onChange: PropTypes.func,
    renderItem: PropTypes.func,
    noResultsMessage: PropTypes.string,
    disableSearch: PropTypes.bool,
    zIndex: PropTypes.number,
    maxHeight: PropTypes.string,
    ...formInputProps,
    ...withMessagesProps
  };
}

export default Select;
<|endoftext|>