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

var events = require('../../events');
var subscribe = events.subscribe;
var publish = events.publish;

var requestGeolocationLatlongs = [];

var LOCATION_TIMEOUT = 20 * 1000;

var requestGeolocationLatlongLayout = {
	init: function() {
		subscribe('layout:request-geolocation-latlong', function(data) {
			var requestGeolocationLatlong = new RequestGeolocationLatlong(data);
			requestGeolocationLatlongs[data.uuid] = requestGeolocationLatlong;
		});
	}
};

function RequestGeolocationLatlong(data) {
	this.init(data);
}

RequestGeolocationLatlong.prototype = {
	init: function(data) {
		this.data = data.data;
		this.uuid = data.uuid;
		this.parentElement = data.element;
		this.layoutElement = data.layoutElement;
		this.timedOut = false;
		this.timeoutCheck = setTimeout(function() {
			this.timedOut = true;
			this.handleLocationNotShared();
		}.bind(this), LOCATION_TIMEOUT);
		publish('enable-loading');
		publish('disable-input');
		navigator.geolocation.getCurrentPosition(
			function(position) {
				if (this.timedOut) return false;
				clearTimeout(this.timeoutCheck);
				this.handleLocationShared(position);
			}.bind(this),
			function() {
				if (this.timedOut) return false;
				clearTimeout(this.timeoutCheck);
				this.handleLocationNotShared();
			}.bind(this),
			{ timeout: LOCATION_TIMEOUT }
		);
	},
	handleLocationShared: function(position) {
		publish('enable-input');
		publish('disable-loading');
		publish('send', {
			text: position.coords.latitude + ',' + position.coords.longitude,
			silent: true
		});
	},
	handleLocationNotShared: function() {
		publish('enable-input');
		publish('disable-loading');
		publish('receive', "You haven't shared your location on this website.");
		publish('send', {
			text: 'find nearest locations',
			silent: true
		});
	}
};

module.exports = requestGeolocationLatlongLayout;
<commit_msg>remove extra timeout<commit_after>/**
* (C) Copyright IBM Corp. 2016. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

var events = require('../../events');
var subscribe = events.subscribe;
var publish = events.publish;

var requestGeolocationLatlongs = [];

var LOCATION_TIMEOUT = 20 * 1000;

var requestGeolocationLatlongLayout = {
	init: function() {
		subscribe('layout:request-geolocation-latlong', function(data) {
			var requestGeolocationLatlong = new RequestGeolocationLatlong(data);
			requestGeolocationLatlongs[data.uuid] = requestGeolocationLatlong;
		});
	}
};

function RequestGeolocationLatlong(data) {
	this.init(data);
}

RequestGeolocationLatlong.prototype = {
	init: function(data) {
		this.data = data.data;
		this.uuid = data.uuid;
		this.parentElement = data.element;
		this.layoutElement = data.layoutElement;
		this.timedOut = false;
		this.timeoutCheck = setTimeout(function() {
			this.timedOut = true;
			this.handleLocationNotShared();
		}.bind(this), LOCATION_TIMEOUT);
		publish('enable-loading');
		publish('disable-input');
		navigator.geolocation.getCurrentPosition(
			function(position) {
				if (this.timedOut) return false;
				clearTimeout(this.timeoutCheck);
				this.handleLocationShared(position);
			}.bind(this),
			function() {
				if (this.timedOut) return false;
				clearTimeout(this.timeoutCheck);
				this.handleLocationNotShared();
			}.bind(this)
		);
	},
	handleLocationShared: function(position) {
		publish('enable-input');
		publish('disable-loading');
		publish('send', {
			text: position.coords.latitude + ',' + position.coords.longitude,
			silent: true
		});
	},
	handleLocationNotShared: function() {
		publish('enable-input');
		publish('disable-loading');
		publish('receive', "You haven't shared your location on this website.");
		publish('send', {
			text: 'find nearest locations',
			silent: true
		});
	}
};

module.exports = requestGeolocationLatlongLayout;
<|endoftext|>


<commit_before>import { LightningElement, api, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import saveAndDryRunDataImport from '@salesforce/apex/GE_GiftEntryController.saveAndDryRunDataImport';
import sendPurchaseRequest from '@salesforce/apex/GE_GiftEntryController.sendPurchaseRequest';
import upsertDataImport from '@salesforce/apex/GE_GiftEntryController.upsertDataImport';
import submitDataImportToBDI from '@salesforce/apex/GE_GiftEntryController.submitDataImportToBDI';

//import GeFormService from 'c/geFormService';
import { showToast } from 'c/utilTemplateBuilder';
import { registerListener, unregisterListener } from 'c/pubsubNoPageRef';
import GeLabelService from 'c/geLabelService';

import DATA_IMPORT_BATCH_OBJECT from '@salesforce/schema/DataImportBatch__c';
import DI_PAYMENT_AUTHORIZE_TOKEN_FIELD from '@salesforce/schema/DataImport__c.Payment_Authorization_Token__c';
import DI_PAYMENT_STATUS_FIELD from '@salesforce/schema/DataImport__c.Payment_Status__c';
import DI_PAYMENT_DECLINED_REASON_FIELD from '@salesforce/schema/DataImport__c.Payment_Declined_Reason__c';
import DI_PAYMENT_METHOD_FIELD from '@salesforce/schema/DataImport__c.Payment_Method__c';
import DI_DONATION_AMOUNT_FIELD from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DI_DONATION_CAMPAIGN_NAME_FIELD from '@salesforce/schema/DataImport__c.Donation_Campaign_Name__c';
import { isNotEmpty } from 'c/utilCommon';

const PAYMENT_STATUS__C = DI_PAYMENT_STATUS_FIELD.fieldApiName;
const PAYMENT_DECLINED_REASON__C = DI_PAYMENT_DECLINED_REASON_FIELD.fieldApiName;
const PAYMENT_AUTHORIZE_TOKEN__C = DI_PAYMENT_AUTHORIZE_TOKEN_FIELD.fieldApiName;
const PAYMENT_METHOD__C = DI_PAYMENT_METHOD_FIELD.fieldApiName;
const DONATION_AMOUNT__C = DI_DONATION_AMOUNT_FIELD.fieldApiName;
const DONATION_CAMPAIGN_NAME__C = DI_DONATION_CAMPAIGN_NAME_FIELD.fieldApiName;
const TOKENIZE_TIMEOUT = 10000; // 10 seconds, long enough for cold starts?
const PAYMENT_TRANSACTION_STATUS_ENUM = Object.freeze({
    PENDING: 'PENDING',
    AUTHORIZED: 'AUTHORIZED',
    CANCELED: 'CANCELED',
    CAPTURED: 'CAPTURED',
    DECLINED: 'DECLINED',
    NONRETRYABLEERROR: 'NONRETRYABLEERROR',
    RETRYABLEERROR: 'RETRYABLEERROR',
    REFUNDISSUED: 'REFUNDISSUED'
});

export default class GeGiftEntryFormApp extends NavigationMixin(LightningElement) {
    @api recordId;
    @api sObjectName;

    @track isPermissionError;
    @track loadingText = 'Saving data import...';

    dataImportRecord = {};
    tokenPromise;
    errorCallback;

    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    connectedCallback() {
        registerListener('tokenRequested', this.handleTokenRequested, this);
    }

    disconnectedCallback() {
        unregisterListener('tokenRequested', this.handleTokenRequested, this);
    }

    get isBatchMode() {
        return this.sObjectName &&
            this.sObjectName === DATA_IMPORT_BATCH_OBJECT.objectApiName;
    }

    handleTokenRequested() {
        this.tokenPromise = new Promise((resolve, reject) => {
            registerListener('tokenResponse', message => {
                if(message.error) {
                    reject(message);
                } else if(message.token) {
                    resolve(message.token);
                }
            }, this);
            setTimeout(() => {
                reject('Request timed out');
                unregisterListener('tokenResponse', resolve, this);
            }, TOKENIZE_TIMEOUT);
        });
    }

    /*******************************************************************************
    * @description Receives a 'submit' event from geFormRenderer and proceeds down
    * the Batch or Single save paths depending on the current app mode.
    *
    * @param {object} event: Custom Event containing the Data Import record and
    * potentially other objects (booleans, callbacks, etc).
    */
    handleSubmit(event) {
        // Callback received from geFormRenderer. Provides functions that
        // toggle the form save button, toggle the lightning spinner, and displays aura exceptions.
        this.errorCallback = event.detail.errorCallback;

        try {
            if (this.isBatchMode) {
                this.batchGiftSubmit(event);
            } else {
                this.singleGiftSubmit(event);
            }
        } catch (error) {
            this.errorCallback(error);
        }
    }

    /*******************************************************************************
    * @description Handles a batch gift entry submit. Saves a Data Import record,
    * runs dry run, and renders the Data Import in the Batch Gift Table.
    *
    * @param {object} event: Custom Event containing the Data Import record and a
    * callback for handling and displaying errors in the form.
    */
    batchGiftSubmit(event) {
        const table = this.template.querySelector('c-ge-batch-gift-entry-table');
        this.dataImportRecord = event.detail.dataImportRecord;
        saveAndDryRunDataImport({batchId: this.recordId, dataImport: this.dataImportRecord})
            .then((result) => {
                let dataImportModel = JSON.parse(result);
                Object.assign(dataImportModel.dataImportRows[0],
                    dataImportModel.dataImportRows[0].record);
                table.upsertData(dataImportModel.dataImportRows[0], 'Id');
                table.setTotalCount(dataImportModel.totalCountOfRows);
                table.setTotalAmount(dataImportModel.totalAmountOfRows);
                event.detail.success(); //Re-enable the Save button
            })
            .catch(error => {
                event.detail.error(error);
            });
    }

    /*******************************************************************************
    * @description Handles a single gift entry submit. Saves a Data Import record,
    * makes an elevate payment if needed, and processes the Data Import through
    * BDI.
    *
    * @param {object} event: Custom Event containing the Data Import record and a
    * callback for handling and displaying errors in the form.
    */
    singleGiftSubmit = async (event) => {
        let { inMemoryDataImport } = event.detail;
        this.hasUserSelectedDonation = event.detail.hasUserSelectedDonation;

        try {
            await this.saveDataImport(inMemoryDataImport);

            const hasPaymentToProcess = this.dataImportRecord[PAYMENT_AUTHORIZE_TOKEN__C];
            if (hasPaymentToProcess) {
                this.processPayment();
            } else {
                this.processDataImport();
            }
        } catch (error) {
            this.errorCallback(error);
        }
    }

    /*******************************************************************************
    * @description Upserts the provided data import record. Attempts to retrieve
    * an Elevate token for a purchase call if needed.
    *
    * @param {object} inMemoryDataImport: DataImport__c object built from the form
    * fields.
    *
    * @return {object} dataImportRecord: A DataImport__c record
    */
    saveDataImport = async (inMemoryDataImport) => {
        if (this.dataImportRecord.Id) {
            this.loadingText = 'Updating Data Import record...';
            inMemoryDataImport = this.prepareInMemoryDataImportForUpdate(inMemoryDataImport);
        } else {
            this.loadingText = 'Saving Data Import record...';
        }

        inMemoryDataImport[PAYMENT_AUTHORIZE_TOKEN__C] = await this.tokenPromise;
        this.dataImportRecord = await upsertDataImport({ dataImport: inMemoryDataImport });
    }

    /*******************************************************************************
    * @description Re-apply Data Import id and relevant payment/elevate fields.
    * The inMemoryDataImport is built new from the form on every save click. We
    * need to catch it up with the correct id to make sure we update instead of
    * inserting a new record on re-save attempts.
    *
    * @param {object} inMemoryDataImport: DataImport__c object built from the form
    * fields.
    *
    * @return {object} inMemoryDataImport: DataImport__c object built from the form
    * fields.
    */
    prepareInMemoryDataImportForUpdate(inMemoryDataImport) {
        inMemoryDataImport.Id = this.dataImportRecord.Id;
        inMemoryDataImport[PAYMENT_METHOD__C] = this.dataImportRecord[PAYMENT_METHOD__C];
        inMemoryDataImport[PAYMENT_STATUS__C] = this.dataImportRecord[PAYMENT_STATUS__C];
        inMemoryDataImport[PAYMENT_DECLINED_REASON__C] =
            this.dataImportRecord[PAYMENT_DECLINED_REASON__C];

        return inMemoryDataImport;
    }
    /*******************************************************************************
    * @description Method attempts to make a purchase call to Payment
    * Services. Immediately attempts to the charge the card provided in the Payment
    * Services iframe (GE_TokenizeCard).
    *
    * @param {object} dataImportRecord: A DataImport__c record
    */
    processPayment = async () => {
        this.loadingText = 'Charging card...';

        const isReadyToCharge = this.checkPaymentTransactionStatus(this.dataImportRecord[PAYMENT_STATUS__C]);
        if (isReadyToCharge) {

            const purchaseResponse = await this.makePurchaseCall();
            if (purchaseResponse) {
                this.dataImportRecord[PAYMENT_STATUS__C] = this.getPaymentStatus(purchaseResponse);
                this.dataImportRecord[PAYMENT_DECLINED_REASON__C] =
                    this.getPaymentDeclinedReason(purchaseResponse);

                this.dataImportRecord = await upsertDataImport({ dataImport: this.dataImportRecord });

                const isFailedPurchase = purchaseResponse.statusCode !== 201;
                if (isFailedPurchase) {
                    let errors = this.getFailedPurchaseMessage(purchaseResponse);
                    this.errorCallback(errors);
                    return;
                }
            }
        }

        this.processDataImport();
    }

    /*******************************************************************************
    * @description Method checks the current payment transaction's status and
    * returns true if the card is in a 'chargeable' status.
    *
    * @param {string} paymentStatus: Payment transaction status
    *
    * @return {boolean}: True if card is in a 'chargeable' status
    */
    checkPaymentTransactionStatus = (paymentStatus) => {
        switch (paymentStatus) {
            case PAYMENT_TRANSACTION_STATUS_ENUM.PENDING: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.AUTHORIZED: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.CANCELED: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.CAPTURED: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.DECLINED: return true;
            case PAYMENT_TRANSACTION_STATUS_ENUM.NONRETRYABLEERROR: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.RETRYABLEERROR: return true;
            case PAYMENT_TRANSACTION_STATUS_ENUM.REFUNDISSUED: return false;
            default: return true;
        }
    }

    /*******************************************************************************
    * @description Posts an http request through the `createTransaction` apex
    * method and parses the response.
    *
    * @param {object} dataImportRecord: A DataImport__c record
    *
    * @return {object} response: An http response object
    */
    makePurchaseCall = async () => {
        let purchaseResponseString = await sendPurchaseRequest({
            requestBodyParameters: this.buildRequestBodyParameters()
        });
        let response = JSON.parse(purchaseResponseString);
        response.body = JSON.parse(response.body);

        return response;
    }

    /*******************************************************************************
    * @description Builds parts of the purchase request body that requires data
    * from the Data Import record upfront. We pass this into the `sendPurchaseRequest`
    * method and is eventually merged in with the rest of the purchase request body.
    *
    * @param {object} dataImportRecord: A DataImport__c record
    *
    * @return {object}: Object that we can deserialize and apply to the purchase
    * request body in apex.
    */
    buildRequestBodyParameters() {
        const names = this.getCardholderNames();
        const firstName = isNotEmpty(names.firstName) ? names.firstName : names.accountName;
        const lastName = isNotEmpty(names.lastName) ? names.lastName : names.accountName;
        const metadata = {
            campaignCode: this.dataImportRecord[DONATION_CAMPAIGN_NAME__C]
        }

        return JSON.stringify({
            amount: this.dataImportRecord[DONATION_AMOUNT__C],
            email: 'test@test.test',
            firstName: firstName,
            lastName: lastName,
            metadata: metadata,
            paymentMethodToken: this.dataImportRecord[PAYMENT_AUTHORIZE_TOKEN__C],
        })
    }

    /*******************************************************************************
    * @description Queries for the geFormRenderer component and retrieves the
    * fabricated cardholder names object.
    *
    * @return {object}: Object containing firstName, lastName, and accountName
    * from the form.
    */
    getCardholderNames() {
        const renderer = this.template.querySelector('c-ge-form-renderer');
        return renderer.fabricatedCardholderNames;
    }

    /*******************************************************************************
    * @description Get the value for DataImport__c.Payment_Status__c from the
    * purchase call response.
    *
    * @param {object} response: Http response object
    *
    * @return {string}: Status of the payment charge request
    */
    getPaymentStatus(response) {
        return response.body.status || response.status || CUSTOM_LABELS.commonUnknownError;
    }

    /*******************************************************************************
    * @description Get the value for DataImport__c.Payment_Declined_Reason__c from
    * the purchase call response.
    *
    * @param {object} response: Http response object
    *
    * @return {string}: Reason the payment was declined
    */
    getPaymentDeclinedReason(response) {
        const isSuccessfulPurchase = response.statusCode === 201;
        return isSuccessfulPurchase ? null : JSON.stringify(response.body);
    }

    /*******************************************************************************
    * @description Get the message or errors from a failed purchase call.
    *
    * @param {object} response: Http response object
    *
    * @return {string}: Message from a failed purchase call response
    */
    getFailedPurchaseMessage(response) {
        // For some reason the key in the body object for 'Message'
        // in the response we receive from Elevate is capitalized.
        // Also checking for lowercase M in message in case they fix it.
        return response.body.Message ||
            response.body.message ||
            response.body.errors.map(error => error.message).join(', ') ||
            this.CUSTOM_LABELS.commonUnknownError;
    }

    /*******************************************************************************
    * @description Sends the Data Import into BDI for processing and navigates to
    * the opportunity record detail page on success.
    *
    * @param {object} dataImportRecord: A DataImport__c record
    * @param {boolean} hasUserSelectedDonation: True if a selection had been made in
    * the 'Review Donations' modal.
    * Determines BDI matching criteria.
    *   true = "single match or create" and means we are updating
    *   false = "do not match"
    */
    processDataImport = async () => {
        this.loadingText = 'Processing data import record...';

        submitDataImportToBDI({ diRecord: this.dataImportRecord, updateGift: this.hasUserSelectedDonation })
            .then(opportunityId => {
                this.loadingText = 'Navigating to opportunity record...';
                this.navigateToRecordPage(opportunityId);
            })
            .catch(error => {
                // TODO: Placeholder method for handling the scenario in which
                // we've charged a card successfully, but BDI processing failed.
                this.doSomethingLoud(error);
            });
    }

    doSomethingLoud() {
        this.errorCallback(error);
        // TODO: Potentially have to check for other types of status that MAY
        // indicate a charge could still occur (pending, authorized, etc)
        if (dataImportRecord[PAYMENT_STATUS__C] === PAYMENT_TRANSACTION_STATUS_ENUM.CAPTURED) {
            showToast('HEY!',
                'Card was charged. Please fix the outstanding errors and try again.',
                'error',
                'sticky');
        }
    }

    handleSectionsRetrieved(event) {
        const formSections = event.target.sections;
        const table = this.template.querySelector('c-ge-batch-gift-entry-table');
        table.handleSectionsRetrieved(formSections);
    }

    handleBatchDryRun() {
        //toggle the spinner on the form
        const form = this.template.querySelector('c-ge-form-renderer');
        const toggleSpinner = function () {
            form.showSpinner = !form.showSpinner
        };
        form.showSpinner = true;

        const table = this.template.querySelector('c-ge-batch-gift-entry-table');
        table.runBatchDryRun(toggleSpinner);
    }

    handleLoadData(event) {
        const form = this.template.querySelector('c-ge-form-renderer');
        form.reset();
        form.load(event.detail);
    }

    handlePermissionErrors() {
        this.isPermissionError = true;
    }
    handleEditBatch() {
        this.dispatchEvent(new CustomEvent('editbatch'));
    }

    handleReviewDonationsModal(event) {
        this.dispatchEvent(new CustomEvent('togglereviewdonationsmodal', { detail: event.detail }));
    }

    navigateToRecordPage(recordId) {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: recordId,
                actionName: 'view'
            }
        });
    }
}<commit_msg>fix missing parameter and bad property reference<commit_after>import { LightningElement, api, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import saveAndDryRunDataImport from '@salesforce/apex/GE_GiftEntryController.saveAndDryRunDataImport';
import sendPurchaseRequest from '@salesforce/apex/GE_GiftEntryController.sendPurchaseRequest';
import upsertDataImport from '@salesforce/apex/GE_GiftEntryController.upsertDataImport';
import submitDataImportToBDI from '@salesforce/apex/GE_GiftEntryController.submitDataImportToBDI';

//import GeFormService from 'c/geFormService';
import { showToast } from 'c/utilTemplateBuilder';
import { registerListener, unregisterListener } from 'c/pubsubNoPageRef';
import GeLabelService from 'c/geLabelService';

import DATA_IMPORT_BATCH_OBJECT from '@salesforce/schema/DataImportBatch__c';
import DI_PAYMENT_AUTHORIZE_TOKEN_FIELD from '@salesforce/schema/DataImport__c.Payment_Authorization_Token__c';
import DI_PAYMENT_STATUS_FIELD from '@salesforce/schema/DataImport__c.Payment_Status__c';
import DI_PAYMENT_DECLINED_REASON_FIELD from '@salesforce/schema/DataImport__c.Payment_Declined_Reason__c';
import DI_PAYMENT_METHOD_FIELD from '@salesforce/schema/DataImport__c.Payment_Method__c';
import DI_DONATION_AMOUNT_FIELD from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DI_DONATION_CAMPAIGN_NAME_FIELD from '@salesforce/schema/DataImport__c.Donation_Campaign_Name__c';
import { isNotEmpty } from 'c/utilCommon';

const PAYMENT_STATUS__C = DI_PAYMENT_STATUS_FIELD.fieldApiName;
const PAYMENT_DECLINED_REASON__C = DI_PAYMENT_DECLINED_REASON_FIELD.fieldApiName;
const PAYMENT_AUTHORIZE_TOKEN__C = DI_PAYMENT_AUTHORIZE_TOKEN_FIELD.fieldApiName;
const PAYMENT_METHOD__C = DI_PAYMENT_METHOD_FIELD.fieldApiName;
const DONATION_AMOUNT__C = DI_DONATION_AMOUNT_FIELD.fieldApiName;
const DONATION_CAMPAIGN_NAME__C = DI_DONATION_CAMPAIGN_NAME_FIELD.fieldApiName;
const TOKENIZE_TIMEOUT = 10000; // 10 seconds, long enough for cold starts?
const PAYMENT_TRANSACTION_STATUS_ENUM = Object.freeze({
    PENDING: 'PENDING',
    AUTHORIZED: 'AUTHORIZED',
    CANCELED: 'CANCELED',
    CAPTURED: 'CAPTURED',
    DECLINED: 'DECLINED',
    NONRETRYABLEERROR: 'NONRETRYABLEERROR',
    RETRYABLEERROR: 'RETRYABLEERROR',
    REFUNDISSUED: 'REFUNDISSUED'
});

export default class GeGiftEntryFormApp extends NavigationMixin(LightningElement) {
    @api recordId;
    @api sObjectName;

    @track isPermissionError;
    @track loadingText = 'Saving data import...';

    dataImportRecord = {};
    tokenPromise;
    errorCallback;

    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    connectedCallback() {
        registerListener('tokenRequested', this.handleTokenRequested, this);
    }

    disconnectedCallback() {
        unregisterListener('tokenRequested', this.handleTokenRequested, this);
    }

    get isBatchMode() {
        return this.sObjectName &&
            this.sObjectName === DATA_IMPORT_BATCH_OBJECT.objectApiName;
    }

    handleTokenRequested() {
        this.tokenPromise = new Promise((resolve, reject) => {
            registerListener('tokenResponse', message => {
                if(message.error) {
                    reject(message);
                } else if(message.token) {
                    resolve(message.token);
                }
            }, this);
            setTimeout(() => {
                reject('Request timed out');
                unregisterListener('tokenResponse', resolve, this);
            }, TOKENIZE_TIMEOUT);
        });
    }

    /*******************************************************************************
    * @description Receives a 'submit' event from geFormRenderer and proceeds down
    * the Batch or Single save paths depending on the current app mode.
    *
    * @param {object} event: Custom Event containing the Data Import record and
    * potentially other objects (booleans, callbacks, etc).
    */
    handleSubmit(event) {
        // Callback received from geFormRenderer. Provides functions that
        // toggle the form save button, toggle the lightning spinner, and displays aura exceptions.
        this.errorCallback = event.detail.errorCallback;

        try {
            if (this.isBatchMode) {
                this.batchGiftSubmit(event);
            } else {
                this.singleGiftSubmit(event);
            }
        } catch (error) {
            this.errorCallback(error);
        }
    }

    /*******************************************************************************
    * @description Handles a batch gift entry submit. Saves a Data Import record,
    * runs dry run, and renders the Data Import in the Batch Gift Table.
    *
    * @param {object} event: Custom Event containing the Data Import record and a
    * callback for handling and displaying errors in the form.
    */
    batchGiftSubmit(event) {
        const table = this.template.querySelector('c-ge-batch-gift-entry-table');
        this.dataImportRecord = event.detail.dataImportRecord;
        saveAndDryRunDataImport({batchId: this.recordId, dataImport: this.dataImportRecord})
            .then((result) => {
                let dataImportModel = JSON.parse(result);
                Object.assign(dataImportModel.dataImportRows[0],
                    dataImportModel.dataImportRows[0].record);
                table.upsertData(dataImportModel.dataImportRows[0], 'Id');
                table.setTotalCount(dataImportModel.totalCountOfRows);
                table.setTotalAmount(dataImportModel.totalAmountOfRows);
                event.detail.success(); //Re-enable the Save button
            })
            .catch(error => {
                event.detail.error(error);
            });
    }

    /*******************************************************************************
    * @description Handles a single gift entry submit. Saves a Data Import record,
    * makes an elevate payment if needed, and processes the Data Import through
    * BDI.
    *
    * @param {object} event: Custom Event containing the Data Import record and a
    * callback for handling and displaying errors in the form.
    */
    singleGiftSubmit = async (event) => {
        let { inMemoryDataImport } = event.detail;
        this.hasUserSelectedDonation = event.detail.hasUserSelectedDonation;

        try {
            await this.saveDataImport(inMemoryDataImport);

            const hasPaymentToProcess = this.dataImportRecord[PAYMENT_AUTHORIZE_TOKEN__C];
            if (hasPaymentToProcess) {
                this.processPayment();
            } else {
                this.processDataImport();
            }
        } catch (error) {
            this.errorCallback(error);
        }
    }

    /*******************************************************************************
    * @description Upserts the provided data import record. Attempts to retrieve
    * an Elevate token for a purchase call if needed.
    *
    * @param {object} inMemoryDataImport: DataImport__c object built from the form
    * fields.
    *
    * @return {object} dataImportRecord: A DataImport__c record
    */
    saveDataImport = async (inMemoryDataImport) => {
        if (this.dataImportRecord.Id) {
            this.loadingText = 'Updating Data Import record...';
            inMemoryDataImport = this.prepareInMemoryDataImportForUpdate(inMemoryDataImport);
        } else {
            this.loadingText = 'Saving Data Import record...';
        }

        inMemoryDataImport[PAYMENT_AUTHORIZE_TOKEN__C] = await this.tokenPromise;
        this.dataImportRecord = await upsertDataImport({ dataImport: inMemoryDataImport });
    }

    /*******************************************************************************
    * @description Re-apply Data Import id and relevant payment/elevate fields.
    * The inMemoryDataImport is built new from the form on every save click. We
    * need to catch it up with the correct id to make sure we update instead of
    * inserting a new record on re-save attempts.
    *
    * @param {object} inMemoryDataImport: DataImport__c object built from the form
    * fields.
    *
    * @return {object} inMemoryDataImport: DataImport__c object built from the form
    * fields.
    */
    prepareInMemoryDataImportForUpdate(inMemoryDataImport) {
        inMemoryDataImport.Id = this.dataImportRecord.Id;
        inMemoryDataImport[PAYMENT_METHOD__C] = this.dataImportRecord[PAYMENT_METHOD__C];
        inMemoryDataImport[PAYMENT_STATUS__C] = this.dataImportRecord[PAYMENT_STATUS__C];
        inMemoryDataImport[PAYMENT_DECLINED_REASON__C] =
            this.dataImportRecord[PAYMENT_DECLINED_REASON__C];

        return inMemoryDataImport;
    }
    /*******************************************************************************
    * @description Method attempts to make a purchase call to Payment
    * Services. Immediately attempts to the charge the card provided in the Payment
    * Services iframe (GE_TokenizeCard).
    *
    * @param {object} dataImportRecord: A DataImport__c record
    */
    processPayment = async () => {
        this.loadingText = 'Charging card...';

        const isReadyToCharge = this.checkPaymentTransactionStatus(this.dataImportRecord[PAYMENT_STATUS__C]);
        if (isReadyToCharge) {

            const purchaseResponse = await this.makePurchaseCall();
            if (purchaseResponse) {
                this.dataImportRecord[PAYMENT_STATUS__C] = this.getPaymentStatus(purchaseResponse);
                this.dataImportRecord[PAYMENT_DECLINED_REASON__C] =
                    this.getPaymentDeclinedReason(purchaseResponse);

                this.dataImportRecord = await upsertDataImport({ dataImport: this.dataImportRecord });

                const isFailedPurchase = purchaseResponse.statusCode !== 201;
                if (isFailedPurchase) {
                    let errors = this.getFailedPurchaseMessage(purchaseResponse);
                    this.errorCallback(errors);
                    return;
                }
            }
        }

        this.processDataImport();
    }

    /*******************************************************************************
    * @description Method checks the current payment transaction's status and
    * returns true if the card is in a 'chargeable' status.
    *
    * @param {string} paymentStatus: Payment transaction status
    *
    * @return {boolean}: True if card is in a 'chargeable' status
    */
    checkPaymentTransactionStatus = (paymentStatus) => {
        switch (paymentStatus) {
            case PAYMENT_TRANSACTION_STATUS_ENUM.PENDING: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.AUTHORIZED: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.CANCELED: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.CAPTURED: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.DECLINED: return true;
            case PAYMENT_TRANSACTION_STATUS_ENUM.NONRETRYABLEERROR: return false;
            case PAYMENT_TRANSACTION_STATUS_ENUM.RETRYABLEERROR: return true;
            case PAYMENT_TRANSACTION_STATUS_ENUM.REFUNDISSUED: return false;
            default: return true;
        }
    }

    /*******************************************************************************
    * @description Posts an http request through the `sendPurchaseRequest` apex
    * method and parses the response.
    *
    * @param {object} dataImportRecord: A DataImport__c record
    *
    * @return {object} response: An http response object
    */
    makePurchaseCall = async () => {
        let purchaseResponseString = await sendPurchaseRequest({
            requestBodyParameters: this.buildRequestBodyParameters()
        });
        let response = JSON.parse(purchaseResponseString);
        response.body = JSON.parse(response.body);

        return response;
    }

    /*******************************************************************************
    * @description Builds parts of the purchase request body that requires data
    * from the Data Import record upfront. We pass this into the `sendPurchaseRequest`
    * method and is eventually merged in with the rest of the purchase request body.
    *
    * @param {object} dataImportRecord: A DataImport__c record
    *
    * @return {object}: Object that we can deserialize and apply to the purchase
    * request body in apex.
    */
    buildRequestBodyParameters() {
        const names = this.getCardholderNames();
        const firstName = isNotEmpty(names.firstName) ? names.firstName : names.accountName;
        const lastName = isNotEmpty(names.lastName) ? names.lastName : names.accountName;
        const metadata = {
            campaignCode: this.dataImportRecord[DONATION_CAMPAIGN_NAME__C]
        }

        return JSON.stringify({
            amount: this.dataImportRecord[DONATION_AMOUNT__C],
            email: 'test@test.test',
            firstName: firstName,
            lastName: lastName,
            metadata: metadata,
            paymentMethodToken: this.dataImportRecord[PAYMENT_AUTHORIZE_TOKEN__C],
        })
    }

    /*******************************************************************************
    * @description Queries for the geFormRenderer component and retrieves the
    * fabricated cardholder names object.
    *
    * @return {object}: Object containing firstName, lastName, and accountName
    * from the form.
    */
    getCardholderNames() {
        const renderer = this.template.querySelector('c-ge-form-renderer');
        return renderer.fabricatedCardholderNames;
    }

    /*******************************************************************************
    * @description Get the value for DataImport__c.Payment_Status__c from the
    * purchase call response.
    *
    * @param {object} response: Http response object
    *
    * @return {string}: Status of the payment charge request
    */
    getPaymentStatus(response) {
        return response.body.status || response.status || CUSTOM_LABELS.commonUnknownError;
    }

    /*******************************************************************************
    * @description Get the value for DataImport__c.Payment_Declined_Reason__c from
    * the purchase call response.
    *
    * @param {object} response: Http response object
    *
    * @return {string}: Reason the payment was declined
    */
    getPaymentDeclinedReason(response) {
        const isSuccessfulPurchase = response.statusCode === 201;
        return isSuccessfulPurchase ? null : JSON.stringify(response.body);
    }

    /*******************************************************************************
    * @description Get the message or errors from a failed purchase call.
    *
    * @param {object} response: Http response object
    *
    * @return {string}: Message from a failed purchase call response
    */
    getFailedPurchaseMessage(response) {
        // For some reason the key in the body object for 'Message'
        // in the response we receive from Elevate is capitalized.
        // Also checking for lowercase M in message in case they fix it.
        return response.body.Message ||
            response.body.message ||
            response.body.errors.map(error => error.message).join(', ') ||
            this.CUSTOM_LABELS.commonUnknownError;
    }

    /*******************************************************************************
    * @description Sends the Data Import into BDI for processing and navigates to
    * the opportunity record detail page on success.
    *
    * @param {object} dataImportRecord: A DataImport__c record
    * @param {boolean} hasUserSelectedDonation: True if a selection had been made in
    * the 'Review Donations' modal.
    * Determines BDI matching criteria.
    *   true = "single match or create" and means we are updating
    *   false = "do not match"
    */
    processDataImport = async () => {
        this.loadingText = 'Processing data import record...';

        submitDataImportToBDI({ diRecord: this.dataImportRecord, updateGift: this.hasUserSelectedDonation })
            .then(opportunityId => {
                this.loadingText = 'Navigating to opportunity record...';
                this.navigateToRecordPage(opportunityId);
            })
            .catch(error => {
                // TODO: Placeholder method for handling the scenario in which
                // we've charged a card successfully, but BDI processing failed.
                this.doSomethingLoud(error);
            });
    }

    doSomethingLoud(error) {
        this.errorCallback(error);
        // TODO: Potentially have to check for other types of status that MAY
        // indicate a charge could still occur (pending, authorized, etc)
        if (this.dataImportRecord[PAYMENT_STATUS__C] === PAYMENT_TRANSACTION_STATUS_ENUM.CAPTURED) {
            showToast('HEY!',
                'Card was charged. Please fix the outstanding errors and try again.',
                'error',
                'sticky');
        }
    }

    handleSectionsRetrieved(event) {
        const formSections = event.target.sections;
        const table = this.template.querySelector('c-ge-batch-gift-entry-table');
        table.handleSectionsRetrieved(formSections);
    }

    handleBatchDryRun() {
        //toggle the spinner on the form
        const form = this.template.querySelector('c-ge-form-renderer');
        const toggleSpinner = function () {
            form.showSpinner = !form.showSpinner
        };
        form.showSpinner = true;

        const table = this.template.querySelector('c-ge-batch-gift-entry-table');
        table.runBatchDryRun(toggleSpinner);
    }

    handleLoadData(event) {
        const form = this.template.querySelector('c-ge-form-renderer');
        form.reset();
        form.load(event.detail);
    }

    handlePermissionErrors() {
        this.isPermissionError = true;
    }
    handleEditBatch() {
        this.dispatchEvent(new CustomEvent('editbatch'));
    }

    handleReviewDonationsModal(event) {
        this.dispatchEvent(new CustomEvent('togglereviewdonationsmodal', { detail: event.detail }));
    }

    navigateToRecordPage(recordId) {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: recordId,
                actionName: 'view'
            }
        });
    }
}<|endoftext|>


<commit_before>const frisby = require('frisby')
const Joi = frisby.Joi
const insecurity = require('../../lib/insecurity')
const http = require('http')
const config = require('config')

const REST_URL = 'http://localhost:3000/rest'

const authHeader = { 'Authorization': 'Bearer ' + insecurity.authorize(), 'content-type': 'application/json' }

describe('/rest/product/:id/reviews', () => {
  const reviewResponseSchema = {
    id: Joi.number(),
    product: Joi.number(),
    message: Joi.string(),
    author: Joi.string()
  }

  it('GET product reviews by product id', done => {
    frisby.get(REST_URL + '/product/1/reviews')
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', reviewResponseSchema).done(done)
  })

  it('GET product reviews attack by injecting a mongoDB sleep command', done => {
    frisby.get(REST_URL + '/product/sleep(1)/reviews')
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', reviewResponseSchema).done(done)
  })

  it('PUT single product review can be created', done => {
    frisby.put(REST_URL + '/product/1/reviews', {
      body: {
        message: 'Lorem Ipsum',
        author: 'Anonymous'
      }
    })
      .expect('status', 201)
      .expect('header', 'content-type', /application\/json/)
      .done(done)
  })
})

describe('/rest/product/reviews', () => {
  const updatedReviewResponseSchema = {
    modified: Joi.number(),
    original: Joi.array(),
    updated: Joi.array()
  }

  let reviewId

  beforeAll((done) => {
    http.get(REST_URL + '/product/1/reviews', (res) => {
      let body = ''

      res.on('data', chunk => {
        body += chunk
      })

      res.on('end', () => {
        const response = JSON.parse(body)
        reviewId = response.data[0]._id
        done()
      })
    })
  })

  it('PATCH single product review can be edited', done => {
    frisby.patch(REST_URL + '/product/reviews', {
      headers: authHeader,
      body: {
        id: reviewId,
        message: 'Lorem Ipsum'
      }
    })
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', updatedReviewResponseSchema)
      .done(done)
  })

  it('PATCH single product review editing need an authenticated user', done => {
    frisby.patch(REST_URL + '/product/reviews', {
      body: {
        id: reviewId,
        message: 'Lorem Ipsum'
      }
    })
      .expect('status', 401)
      .done(done)
  })

  it('PATCH multiple product review via injection', done => {
    // Count all the reviews. (Count starts at one because of the review inserted by the other tests...)
    const totalReviews = config.get('products').reduce((sum, {reviews = []}) => sum + reviews.length, 1)

    frisby.patch(REST_URL + '/product/reviews', {
      headers: authHeader,
      body: {
        id: { '$ne': -1 },
        message: 'trololololololololololololololololololololololololololol'
      }
    })
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', updatedReviewResponseSchema)
      .expect('json', { modified: totalReviews })
      .done(done)
  })
})
<commit_msg>Apply standard styling<commit_after>const frisby = require('frisby')
const Joi = frisby.Joi
const insecurity = require('../../lib/insecurity')
const http = require('http')
const config = require('config')

const REST_URL = 'http://localhost:3000/rest'

const authHeader = { 'Authorization': 'Bearer ' + insecurity.authorize(), 'content-type': 'application/json' }

describe('/rest/product/:id/reviews', () => {
  const reviewResponseSchema = {
    id: Joi.number(),
    product: Joi.number(),
    message: Joi.string(),
    author: Joi.string()
  }

  it('GET product reviews by product id', done => {
    frisby.get(REST_URL + '/product/1/reviews')
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', reviewResponseSchema).done(done)
  })

  it('GET product reviews attack by injecting a mongoDB sleep command', done => {
    frisby.get(REST_URL + '/product/sleep(1)/reviews')
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', reviewResponseSchema).done(done)
  })

  it('PUT single product review can be created', done => {
    frisby.put(REST_URL + '/product/1/reviews', {
      body: {
        message: 'Lorem Ipsum',
        author: 'Anonymous'
      }
    })
      .expect('status', 201)
      .expect('header', 'content-type', /application\/json/)
      .done(done)
  })
})

describe('/rest/product/reviews', () => {
  const updatedReviewResponseSchema = {
    modified: Joi.number(),
    original: Joi.array(),
    updated: Joi.array()
  }

  let reviewId

  beforeAll((done) => {
    http.get(REST_URL + '/product/1/reviews', (res) => {
      let body = ''

      res.on('data', chunk => {
        body += chunk
      })

      res.on('end', () => {
        const response = JSON.parse(body)
        reviewId = response.data[0]._id
        done()
      })
    })
  })

  it('PATCH single product review can be edited', done => {
    frisby.patch(REST_URL + '/product/reviews', {
      headers: authHeader,
      body: {
        id: reviewId,
        message: 'Lorem Ipsum'
      }
    })
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', updatedReviewResponseSchema)
      .done(done)
  })

  it('PATCH single product review editing need an authenticated user', done => {
    frisby.patch(REST_URL + '/product/reviews', {
      body: {
        id: reviewId,
        message: 'Lorem Ipsum'
      }
    })
      .expect('status', 401)
      .done(done)
  })

  it('PATCH multiple product review via injection', done => {
    // Count all the reviews. (Count starts at one because of the review inserted by the other tests...)
    const totalReviews = config.get('products').reduce((sum, {reviews = []}) => sum + reviews.length, 1)

    frisby.patch(REST_URL + '/product/reviews', {
      headers: authHeader,
      body: {
        id: { '$ne': -1 },
        message: 'trololololololololololololololololololololololololololol'
      }
    })
      .expect('status', 200)
      .expect('header', 'content-type', /application\/json/)
      .expect('jsonTypes', updatedReviewResponseSchema)
      .expect('json', { modified: totalReviews })
      .done(done)
  })
})
<|endoftext|>


<commit_before>import TestUtils from 'react-addons-test-utils'
import SearchBar from '../../src/components/SearchBar'

import React from 'react' // TODO: Why is this required??

function setup() {
  const actions = {
    search: jasmine.createSpy('search')
  };
  const component = TestUtils.renderIntoDocument(<SearchBar {...actions} />)
  return {
    component: component,
    actions: actions,
    input: TestUtils.findRenderedDOMComponentWithTag(component, 'input')
  };
}

describe('SearchBar component', () => {
  it('calls search on text input', () => {
    const { input, actions } = setup()
    TestUtils.Simulate.change(input)
    expect(actions.search).toHaveBeenCalled()
  })
})
<commit_msg>Green test<commit_after>import TestUtils from 'react-addons-test-utils'
import SearchBar from '../../src/components/SearchBar'

import React from 'react' // TODO: Why is this required??

function setup() {
  const actions = {
    search: jasmine.createSpy('search')
  };
  const component = TestUtils.renderIntoDocument(<SearchBar {...actions} />)
  return {
    component: component,
    actions: actions,
    input: TestUtils.findRenderedDOMComponentWithTag(component, 'input')
  };
}

describe('SearchBar component', () => {
  it('triggers the search action on text input', () => {
    const { input, actions } = setup()
    TestUtils.Simulate.change(input)
    expect(actions.search).toHaveBeenCalled()
  })
})
<|endoftext|>


<commit_before>import { renderComponent , expect } from '../testHelper';
import AppHeader from '../../src/components/appHeader';

describe('AppHeader' , () => {
  let component;

  beforeEach(() => {
    component = renderComponent(AppHeader);
  });

  it('is rendered successfully', () => {
    expect(component).to.exist;
  });

  it('should have the title of "Icebox"', () => {
  	expect(component).to.contain('Icebox');
  });

  describe('AppHeader interactivity', () => {
  	xit('should show the side drawer when the MenuIconButton is clicked', () => {

  	});

  	describe('when no user signed in', () => {
  		beforeEach(() => {
  			component = renderComponent(AppHeader, null, { auth: false });
  		});

  		it('should have a signup button', () => {
  			expect(component.find('.signup-button')).to.exist;
  		});

  		it('should have a signin button', () => {
  			expect(component.find('.signin-button')).to.exist;
  		});
  	});

  	describe('when a user is signed in', () => {
  		beforeEach(() => {
  			component = renderComponent(AppHeader, null, { auth: true });
  		});

  		it('should have a help button', () => {
  			expect(component.find('.help-button')).to.exist;
  		});

  		it('should have a signout button', () => {
  			expect(component.find('.signout-button')).to.exist;
  		});
  	});
  });

});
<commit_msg>[pull] creates tests for appDrawer interaction in appHeader<commit_after>import { renderComponent , expect } from '../testHelper';
import AppHeader from '../../src/components/appHeader';

describe('AppHeader' , () => {
  let component;

  beforeEach(() => {
    component = renderComponent(AppHeader);
  });

  it('is rendered successfully', () => {
    expect(component).to.exist;
  });

  it('should have the title of "Icebox"', () => {
  	expect(component).to.contain('Icebox');
  });

  describe('AppHeader interactivity', () => {
  	it('should hide the side drawer by default', () => {
  		// expect(component.find('.app-drawer-overlay')).to.be.disabled;
  		// expect(component.find('.app-drawer-overlay')).to.have.css('opacity','0');
  		expect(component.find('.app-drawer-component')).to.exist;
  	});

  	it('should show the side drawer when the MenuIconButton is clicked', () => {
  		component.find('.appheader-menu-button').simulate('click');
  		// expect(component.find('.app-drawer-overlay')).to.be.visible;
  		// expect(component.find('.app-drawer-overlay')).to.have.css('opacity','1');
  		expect(component.find('.app-drawer-component')).to.exist;
  	});

  	describe('when no user signed in', () => {
  		beforeEach(() => {
  			component = renderComponent(AppHeader, null, { auth: false });
  		});

  		it('should have a signup button', () => {
  			expect(component.find('.signup-button')).to.exist;
  		});

  		it('should have a signin button', () => {
  			expect(component.find('.signin-button')).to.exist;
  		});
  	});

  	describe('when a user is signed in', () => {
  		beforeEach(() => {
  			component = renderComponent(AppHeader, null, { auth: true });
  		});

  		it('should have a help button', () => {
  			expect(component.find('.help-button')).to.exist;
  		});

  		it('should have a signout button', () => {
  			expect(component.find('.signout-button')).to.exist;
  		});
  	});
  });

});
<|endoftext|>


<commit_before>import should from "should";

import daily from "../../controllers/daily";
import dbhelper from "../../controllers/dbhelper";
import CatalogController from "../../controllers/catalog";

describe("controllers/catalog", function ()
{
    before(function (done)
    {
        if (dbhelper.connected())
        {
            dbhelper.dropAllCollections(done);
        }
        else
        {
            dbhelper.connect(function (err)
            {
                should.not.exist(err);
                dbhelper.dropAllCollections(done);
            });
        }
    });

    let ids = [];
    const date = "20151214";
    describe("1.saveCatalog", function ()
    {
        it("should save the catalog of date: " + date, function (done)
        {
            daily.fetchStoryIDs(date, function (err, res)
            {
                should.not.exist(err);
                ids = res.ids;
                CatalogController.saveCatalog(res, function (err, doc)
                {
                    should.not.exist(err);
                    doc.date.should.equal(date);
                    doc.ids.join("").should.equal(ids.join(""));
                    done();
                });
            });
        });
    });

    describe("2.findCatalogByDate", function ()
    {
        it("should find the catalog of date: " + date, function (done)
        {
            CatalogController.findCatalogByDate(date, function (err, doc)
            {
                should.not.exist(err);
                doc.date.should.equal(date);
                doc.ids.join("").should.equal(ids.join(""));
                done();
            });
        });
    });
});
<commit_msg>update<commit_after>import should from "should";

import daily from "../../controllers/daily";
import dbhelper from "../../controllers/dbhelper";
import CatalogController from "../../controllers/catalog";

describe("controllers/catalog", function ()
{
    before(function (done)
    {
        dbhelper.start((err) =>
        {
            if (dbhelper.connected())
            {
                dbhelper.dropAllCollections(done);
            }
            else
            {
                dbhelper.connect(function (err)
                {
                    should.not.exist(err);
                    dbhelper.dropAllCollections(done);
                });
            }
        });
    });

    let ids = [];
    const date = "20151214";
    describe("1.saveCatalog", function ()
    {
        it("should save the catalog of date: " + date, function (done)
        {
            daily.fetchStoryIDs(date, function (err, res)
            {
                should.not.exist(err);
                ids = res.ids;
                CatalogController.saveCatalog(res, function (err, doc)
                {
                    should.not.exist(err);
                    doc.date.should.equal(date);
                    doc.ids.join("").should.equal(ids.join(""));
                    done();
                });
            });
        });
    });

    describe("2.findCatalogByDate", function ()
    {
        it("should find the catalog of date: " + date, function (done)
        {
            CatalogController.findCatalogByDate(date, function (err, doc)
            {
                should.not.exist(err);
                doc.date.should.equal(date);
                doc.ids.join("").should.equal(ids.join(""));
                done();
            });
        });
    });
});
<|endoftext|>


<commit_before>var steal = require("@steal");
var loader = require("@loader");

var localLoader = loader.localLoader || loader;
localLoader.slimConfig.toMap.push("~/app.css!");
localLoader.slimConfig.needsDynamicLoader = true;

function CSSModule(address, source) {
	this.address = address;
	this.source = source;
}

CSSModule.prototype.injectStyle = function() {
	var head = document.head;
	var style = document.createElement("style");

	style.type = "text/css";
	style.styleSheet.cssText = this.source;

	head.appendChild(style);
};

exports.instantiate = function(load) {
	var loader = this;
	var css = new CSSModule(load.address, load.source);

	load.metadata.deps = [];
	load.metadata.format = "css";
	load.metadata.execute = function() {
		css.injectStyle();

		return loader.newModule({
			source: css.source
		});
	};
};

exports.buildType = "css";
exports.includeInBuild = true;
exports.pluginBuilder = "slim-css";
<commit_msg>Remove unused file<commit_after><|endoftext|>


<commit_before>import EmberDebug from "ember_debug";
import PromiseAssembler from "libs/promise_assembler";

var port, name, message, RSVP = Ember.RSVP;

module("Promise Debug", {
  setup: function() {

    EmberDebug.Port = EmberDebug.Port.extend({
      init: function() {},
      send: function(n, m) {
        name = n;
        message = m;
      }
    });

    Ember.run(function() {
      EmberDebug.promiseAssembler = PromiseAssembler.create();
    });
    Ember.run(EmberDebug.promiseAssembler, 'start');
    App.reset();
    Ember.run(EmberDebug, 'start');
    EmberDebug.get('promiseDebug').reopen({
      delay: 5
    });
    port = EmberDebug.port;
  },
  teardown: function() {
    Ember.run(EmberDebug.get('promiseDebug'), 'destroy');
    Ember.promiseDebug = null;
    Ember.run(EmberDebug.get('promiseAssembler'), 'destroy');
    EmberDebug.promiseAssembler = null;
    name = null;
    message = null;
  }
});

test("Existing promises sent when requested", function() {
  var promise1, child1, promise2;

  Ember.run(function() {
    RSVP.resolve('value', "Promise1")
    .then(null, null, "Child1");

    RSVP.reject('reason', "Promise2");
  });

  port.trigger('promise:getAndObservePromises');

  equal(name, 'promise:promisesUpdated');

  var promises = message.promises;
  equal(promises.length, 3);

  promise1 = promises[0];
  child1 = promises[1];
  promise2 = promises[2];

  equal(promise1.label, 'Promise1');
  equal(promise1.state, 'fulfilled');
  equal(promise1.children.length, 1);
  equal(promise1.children[0], child1.guid);

  equal(child1.label, 'Child1');
  equal(child1.state, 'fulfilled');
  equal(child1.parent, promise1.guid);

  equal(promise2.label, 'Promise2');
  equal(promise2.state, 'rejected');

});

test("Updates are published when they happen", function() {
  port.trigger('promise:getAndObservePromises');

  var p = new Ember.RSVP.Promise(function() {}, "Promise1");

  equal(name, 'promise:promisesUpdated');
  stop();
  Ember.run.later(function() {
    var promises = message.promises;
    promises.length = 1;
    var promise = message.promises[0];
    equal(promise.label, 'Promise1');
    p.then(null, null, "Child1");
    Ember.run.later(function() {
      start();
      equal(name, 'promise:promisesUpdated');
      equal(message.promises.length, 2);
      var child = message.promises[0];
      equal(child.parent, promise.guid);
      equal(child.label, 'Child1');
      var parent = message.promises[1];
      equal(parent.guid, promise.guid);
    }, 50);

  }, 50);
});
<commit_msg>View tree test passing<commit_after>import EmberDebug from "ember_debug";
import PromiseAssembler from "libs/promise_assembler";

var port, name, message, RSVP = Ember.RSVP;

module("Promise Debug", {
  setup: function() {

    EmberDebug.Port = EmberDebug.Port.extend({
      init: function() {},
      send: function(n, m) {
        name = n;
        message = m;
      }
    });

    Ember.run(function() {
      EmberDebug.promiseAssembler = PromiseAssembler.create();
    });
    Ember.run(EmberDebug.promiseAssembler, 'start');
    App.reset();
    Ember.run(EmberDebug, 'start');
    EmberDebug.get('promiseDebug').reopen({
      delay: 5
    });
    port = EmberDebug.port;
  },
  teardown: function() {
    Ember.run(EmberDebug.get('promiseDebug'), 'destroy');
    Ember.promiseDebug = null;
    Ember.run(EmberDebug.get('promiseAssembler'), 'destroy');
    EmberDebug.promiseAssembler = null;
    name = null;
    message = null;
  }
});

test("Existing promises sent when requested", function() {
  var promise1, child1, promise2;

  Ember.run(function() {
    RSVP.resolve('value', "Promise1")
    .then(null, null, "Child1");

    RSVP.reject('reason', "Promise2");
  });

  port.trigger('promise:getAndObservePromises');

  equal(name, 'promise:promisesUpdated');

  var promises = message.promises;
  equal(promises.length, 3);

  promise1 = promises[0];
  child1 = promises[1];
  promise2 = promises[2];

  equal(promise1.label, 'Promise1');
  equal(promise1.state, 'fulfilled');
  equal(promise1.children.length, 1);
  equal(promise1.children[0], child1.guid);

  equal(child1.label, 'Child1');
  equal(child1.state, 'fulfilled');
  equal(child1.parent, promise1.guid);

  equal(promise2.label, 'Promise2');
  equal(promise2.state, 'rejected');

});

test("Updates are published when they happen", function() {
  port.trigger('promise:getAndObservePromises');

  var p = new Ember.RSVP.Promise(function() {}, "Promise1");

  equal(name, 'promise:promisesUpdated');
  stop();
  Ember.run.later(function() {
    var promises = message.promises;
    promises.length = 1;
    var promise = message.promises[0];
    equal(promise.label, 'Promise1');
    p.then(null, null, "Child1");
    Ember.run.later(function() {
      start();
      equal(name, 'promise:promisesUpdated');
      equal(message.promises.length, 2);
      var child = message.promises[0];
      equal(child.parent, promise.guid);
      equal(child.label, 'Child1');
      var parent = message.promises[1];
      equal(parent.guid, promise.guid);
    }, 100);

  }, 100);
});
<|endoftext|>


<commit_before>/**
 * Created by nikulkarni on 8/18/14.
 */

var Nemo = require('nemo'),
    plugins = require('../config/nemo-plugins'),
    config = {
        "view": ['paypalRegistration']
    };
process.env["PATH"] += ":"+process.cwd()+"/test/functional/resources/chrome_binary"
process.env.nemoData = JSON.stringify({
    targetBrowser: "firefox",
    targetServer: "localhost",
    serverProps: {"port": 4444},
    seleniumJar: "/usr/local/bin/selenium-server-standalone.jar",
    targetBaseUrl: "https://www.paypal.com",
    autoBaseDir:process.cwd() + "/" + "test/functional"
});

var WorldConstructor = function WorldConstructor(callback) {
    this.url = 'https://www.paypal.com/signup/account#personalAccountSignUp';
    var that = this;
    (new Nemo(plugins)).setup(config).then(function (cb_nemo) {
        that.nemo = cb_nemo;
        callback();
    })
};
exports.World = WorldConstructor;<commit_msg>removed chrome path<commit_after>/**
 * Created by nikulkarni on 8/18/14.
 */

var Nemo = require('nemo'),
    plugins = require('../config/nemo-plugins'),
    config = {
        "view": ['paypalRegistration']
    };
process.env.nemoData = JSON.stringify({
    targetBrowser: "firefox",
    targetServer: "localhost",
    serverProps: {"port": 4444},
    seleniumJar: "/usr/local/bin/selenium-server-standalone.jar",
    targetBaseUrl: "https://www.paypal.com",
    autoBaseDir:process.cwd() + "/" + "test/functional"
});

var WorldConstructor = function WorldConstructor(callback) {
    this.url = 'https://www.paypal.com/signup/account#personalAccountSignUp';
    var that = this;
    (new Nemo(plugins)).setup(config).then(function (cb_nemo) {
        that.nemo = cb_nemo;
        callback();
    })
};
exports.World = WorldConstructor;<|endoftext|>


<commit_before>//remove:
require('../build/get-own-property-symbols.node.js');
//:remove

/*! https://mths.be/fromcodepoint v0.2.1 by @mathias */
if (!String.fromCodePoint) {
  (function() {
    var defineProperty = (function() {
      // IE 8 only supports `Object.defineProperty` on DOM elements
      try {
        var object = {};
        var $defineProperty = Object.defineProperty;
        var result = $defineProperty(object, object, object) && $defineProperty;
      } catch(error) {}
      return result;
    }());
    var stringFromCharCode = String.fromCharCode;
    var floor = Math.floor;
    var fromCodePoint = function(_) {
      var MAX_SIZE = 0x4000;
      var codeUnits = [];
      var highSurrogate;
      var lowSurrogate;
      var index = -1;
      var length = arguments.length;
      if (!length) {
        return '';
      }
      var result = '';
      while (++index < length) {
        var codePoint = Number(arguments[index]);
        if (
          !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
          codePoint < 0 || // not a valid Unicode code point
          codePoint > 0x10FFFF || // not a valid Unicode code point
          floor(codePoint) != codePoint // not an integer
        ) {
          throw RangeError('Invalid code point: ' + codePoint);
        }
        if (codePoint <= 0xFFFF) { // BMP code point
          codeUnits.push(codePoint);
        } else { // Astral code point; split in surrogate halves
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          codePoint -= 0x10000;
          highSurrogate = (codePoint >> 10) + 0xD800;
          lowSurrogate = (codePoint % 0x400) + 0xDC00;
          codeUnits.push(highSurrogate, lowSurrogate);
        }
        if (index + 1 == length || codeUnits.length > MAX_SIZE) {
          result += stringFromCharCode.apply(null, codeUnits);
          codeUnits.length = 0;
        }
      }
      return result;
    };
    if (defineProperty) {
      defineProperty(String, 'fromCodePoint', {
        'value': fromCodePoint,
        'configurable': true,
        'writable': true
      });
    } else {
      String.fromCodePoint = fromCodePoint;
    }
  }());
}

/*! https://mths.be/codepointat v0.2.0 by @mathias */
if (!String.prototype.codePointAt) {
  (function() {
    'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
    var defineProperty = (function() {
      // IE 8 only supports `Object.defineProperty` on DOM elements
      try {
        var object = {};
        var $defineProperty = Object.defineProperty;
        var result = $defineProperty(object, object, object) && $defineProperty;
      } catch(error) {}
      return result;
    }());
    var codePointAt = function(position) {
      if (this == null) {
        throw TypeError();
      }
      var string = String(this);
      var size = string.length;
      // `ToInteger`
      var index = position ? Number(position) : 0;
      if (index != index) { // better `isNaN`
        index = 0;
      }
      // Account for out-of-bounds indices:
      if (index < 0 || index >= size) {
        return undefined;
      }
      // Get the first code unit
      var first = string.charCodeAt(index);
      var second;
      if ( // check if it’s the start of a surrogate pair
        first >= 0xD800 && first <= 0xDBFF && // high surrogate
        size > index + 1 // there is a next code unit
      ) {
        second = string.charCodeAt(index + 1);
        if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
        }
      }
      return first;
    };
    if (defineProperty) {
      defineProperty(String.prototype, 'codePointAt', {
        'value': codePointAt,
        'configurable': true,
        'writable': true
      });
    } else {
      String.prototype.codePointAt = codePointAt;
    }
  }());
}

wru.test([
  {
    name: 'main',
    test: function () {
      wru.assert(typeof Object.getOwnPropertySymbols == 'function');
      wru.assert(typeof Symbol == 'function');
    }
  }, {
    name: 'basics work',
    test: function () {
      var o = {};
      var s = Symbol();
      o[s] = 123;
      wru.assert('no visible keys', Object.keys(o).join('') === '');
      wru.assert('still reachable value', o[s] === 123);
      for (var key in o) {
        throw new Error('this should not happen');
      }
      wru.assert('can be retrieved', Object.getOwnPropertySymbols(o)[0] === s);
      delete o[s];
      wru.assert('can be removed', Object.getOwnPropertySymbols(o).length === 0);
      o[s] = 456;
      wru.assert('can be added back', o[s] === 456);
    }
  }, {
    name: 'cannot use Symbol as constructor',
    test: function () {
      try {
        new Symbol;
        wru.assert(false);
      } catch(e) {
        wru.assert('OK, it threw an error');
      }
    }
  }, {
    name: 'Symbol name',
    test: function () {
      var s1 = Symbol();
      var s2 = Symbol('test2');
      wru.assert('it is possible to name one', -1 < s2.valueOf().toString().indexOf('test2'));

    }
  }, {
    name: 'Symbol methods',
    test: function () {
      var label = 'random' + Math.random();
      var s = Symbol['for'](label);
      wru.assert('labeled Symbols are the same', s === Symbol['for'](label));
      wru.assert('labels can be retrieved back', Symbol.keyFor(s) === label);
    }
  }, {
    name: 'propertyIsEnumerable',
    test: function () {
      var o = {};
      var s = Symbol();
      o[s] = 123;
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is enumerable', o.propertyIsEnumerable(s));
    }
  }, {
    name: 'define Symbol property as enumerable',
    test: function () {
      var o = {};
      var s = Symbol();
      Object.defineProperty(o, s, {enumerable: true, value: 456});
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is enumerable', o.propertyIsEnumerable(s));
    }
  }, {
    name: 'define Symbol property as non enumerable',
    test: function () {
      var o = {};
      var s = Symbol();
      Object.defineProperty(o, s, {enumerable: false, value: 456});
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is NOT enumerable', !o.propertyIsEnumerable(s));
    }
  }, {
    name: 'defineProperties',
    test: function () {
      var o = {};
      var descriptors = {};
      var sEnumerable = Symbol();
      var sNotEnumerable = Symbol();
      descriptors[sEnumerable] = {
        enumerable: true,
        value: 'enumerable'
      };
      descriptors[sNotEnumerable] = {
        value: 'non-enumerable'
      };
      Object.defineProperties(o, descriptors);
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is enumerable', o.propertyIsEnumerable(sEnumerable));
      wru.assert('property is NOT enumerable', !o.propertyIsEnumerable(sNotEnumerable));
      wru.assert('enumerable property is the right one', o[sEnumerable] === 'enumerable');
      wru.assert('non enumerable property is the right one', o[sNotEnumerable] === 'non-enumerable');
    }
  }, {
    name: 'defineProperties with non enumerable properties',
    test: function () {
      var o = {};
      var s = Symbol();
      var descriptors = Object.defineProperty({}, s, {enumerable: false, value: 'would throw'});
      Object.defineProperties(o, descriptors);
      wru.assert('should not have been assigned', o[s] === undefined);
    }
  }, {
    name: 'Array.prototype[Symbol.iterator]',
    test: function () {
      var iterator = [1,2,3][Symbol.iterator]();
      var list = [];
      var result;
      while (!(result = iterator.next()).done) {
        list.push(result.value);
      }
      wru.assert('Array is iterable', list.join(',') === '1,2,3');
      for (var k in []) throw new Error('there should be no iterator here');
    }
  }, {
    name: 'String.prototype[Symbol.iterator]',
    test: function () {
      var iterator = '😺😲'[Symbol.iterator]();
      var list = [];
      var result;
      while (!(result = iterator.next()).done) {
        list.push(result.value);
      }
      wru.assert('String is iterable', list.join(',') === '😺,😲');
      for (var k in '') throw new Error('there should be no iterator here');
    }
  }, {
    name: 'getOwnPropertyDescriptor',
    test: function () {
      var s = Symbol('gOPD');
      var a = {};
      var b = {};
      a[s] = true;
      Object.defineProperty(b, s, {value: false});
      wru.assert('a has it enumerable', a.propertyIsEnumerable(s));
      wru.assert('b has NOT it enumerable', !b.propertyIsEnumerable(s));
      wru.assert('a has descriptor enumerable', Object.getOwnPropertyDescriptor(a, s).enumerable);
      wru.assert('b has NOT descriptor enumerable', !Object.getOwnPropertyDescriptor(b, s).enumerable);
    }
  }, {
    name: 'Object.create(proto, symbols)',
    test: function () {
      var s = Symbol('Object.create');
      var a = {};
      var descriptors = {test: {value: 'value'}};
      descriptors[s] = {value: 'symbol'};
      var b = Object.create(a, descriptors);
      wru.assert('create works', a.isPrototypeOf(b));
      wru.assert('descriptors work', b.hasOwnProperty('test'));
      wru.assert('descriptors symbols work', b.hasOwnProperty(s));
      wru.assert('for real', b[s] === 'symbol');
    }
  }, {
    name: 'typeof is not string',
    test: function () {
      wru.assert(typeof Symbol('typeof') !== 'string');
    }
  }, {
    name: 'instanceof is not Symbol',
    test: function () {
      wru.assert(!(Symbol('instanceof') instanceof Symbol));
    }
  }, {
    name: 'Symbol().constructor',
    test: function () {
      wru.assert(Symbol('constructor').constructor === Symbol);
    }
  }
]);
<commit_msg>added basic test for Object.create<commit_after>//remove:
require('../build/get-own-property-symbols.node.js');
//:remove

/*! https://mths.be/fromcodepoint v0.2.1 by @mathias */
if (!String.fromCodePoint) {
  (function() {
    var defineProperty = (function() {
      // IE 8 only supports `Object.defineProperty` on DOM elements
      try {
        var object = {};
        var $defineProperty = Object.defineProperty;
        var result = $defineProperty(object, object, object) && $defineProperty;
      } catch(error) {}
      return result;
    }());
    var stringFromCharCode = String.fromCharCode;
    var floor = Math.floor;
    var fromCodePoint = function(_) {
      var MAX_SIZE = 0x4000;
      var codeUnits = [];
      var highSurrogate;
      var lowSurrogate;
      var index = -1;
      var length = arguments.length;
      if (!length) {
        return '';
      }
      var result = '';
      while (++index < length) {
        var codePoint = Number(arguments[index]);
        if (
          !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
          codePoint < 0 || // not a valid Unicode code point
          codePoint > 0x10FFFF || // not a valid Unicode code point
          floor(codePoint) != codePoint // not an integer
        ) {
          throw RangeError('Invalid code point: ' + codePoint);
        }
        if (codePoint <= 0xFFFF) { // BMP code point
          codeUnits.push(codePoint);
        } else { // Astral code point; split in surrogate halves
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          codePoint -= 0x10000;
          highSurrogate = (codePoint >> 10) + 0xD800;
          lowSurrogate = (codePoint % 0x400) + 0xDC00;
          codeUnits.push(highSurrogate, lowSurrogate);
        }
        if (index + 1 == length || codeUnits.length > MAX_SIZE) {
          result += stringFromCharCode.apply(null, codeUnits);
          codeUnits.length = 0;
        }
      }
      return result;
    };
    if (defineProperty) {
      defineProperty(String, 'fromCodePoint', {
        'value': fromCodePoint,
        'configurable': true,
        'writable': true
      });
    } else {
      String.fromCodePoint = fromCodePoint;
    }
  }());
}

/*! https://mths.be/codepointat v0.2.0 by @mathias */
if (!String.prototype.codePointAt) {
  (function() {
    'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
    var defineProperty = (function() {
      // IE 8 only supports `Object.defineProperty` on DOM elements
      try {
        var object = {};
        var $defineProperty = Object.defineProperty;
        var result = $defineProperty(object, object, object) && $defineProperty;
      } catch(error) {}
      return result;
    }());
    var codePointAt = function(position) {
      if (this == null) {
        throw TypeError();
      }
      var string = String(this);
      var size = string.length;
      // `ToInteger`
      var index = position ? Number(position) : 0;
      if (index != index) { // better `isNaN`
        index = 0;
      }
      // Account for out-of-bounds indices:
      if (index < 0 || index >= size) {
        return undefined;
      }
      // Get the first code unit
      var first = string.charCodeAt(index);
      var second;
      if ( // check if it’s the start of a surrogate pair
        first >= 0xD800 && first <= 0xDBFF && // high surrogate
        size > index + 1 // there is a next code unit
      ) {
        second = string.charCodeAt(index + 1);
        if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
        }
      }
      return first;
    };
    if (defineProperty) {
      defineProperty(String.prototype, 'codePointAt', {
        'value': codePointAt,
        'configurable': true,
        'writable': true
      });
    } else {
      String.prototype.codePointAt = codePointAt;
    }
  }());
}

wru.test([
  {
    name: 'main',
    test: function () {
      wru.assert(typeof Object.getOwnPropertySymbols == 'function');
      wru.assert(typeof Symbol == 'function');
    }
  }, {
    name: 'basics work',
    test: function () {
      var o = {};
      var s = Symbol();
      o[s] = 123;
      wru.assert('no visible keys', Object.keys(o).join('') === '');
      wru.assert('still reachable value', o[s] === 123);
      for (var key in o) {
        throw new Error('this should not happen');
      }
      wru.assert('can be retrieved', Object.getOwnPropertySymbols(o)[0] === s);
      delete o[s];
      wru.assert('can be removed', Object.getOwnPropertySymbols(o).length === 0);
      o[s] = 456;
      wru.assert('can be added back', o[s] === 456);
    }
  }, {
    name: 'cannot use Symbol as constructor',
    test: function () {
      try {
        new Symbol;
        wru.assert(false);
      } catch(e) {
        wru.assert('OK, it threw an error');
      }
    }
  }, {
    name: 'Symbol name',
    test: function () {
      var s1 = Symbol();
      var s2 = Symbol('test2');
      wru.assert('it is possible to name one', -1 < s2.valueOf().toString().indexOf('test2'));

    }
  }, {
    name: 'Symbol methods',
    test: function () {
      var label = 'random' + Math.random();
      var s = Symbol['for'](label);
      wru.assert('labeled Symbols are the same', s === Symbol['for'](label));
      wru.assert('labels can be retrieved back', Symbol.keyFor(s) === label);
    }
  }, {
    name: 'propertyIsEnumerable',
    test: function () {
      var o = {};
      var s = Symbol();
      o[s] = 123;
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is enumerable', o.propertyIsEnumerable(s));
    }
  }, {
    name: 'define Symbol property as enumerable',
    test: function () {
      var o = {};
      var s = Symbol();
      Object.defineProperty(o, s, {enumerable: true, value: 456});
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is enumerable', o.propertyIsEnumerable(s));
    }
  }, {
    name: 'define Symbol property as non enumerable',
    test: function () {
      var o = {};
      var s = Symbol();
      Object.defineProperty(o, s, {enumerable: false, value: 456});
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is NOT enumerable', !o.propertyIsEnumerable(s));
    }
  }, {
    name: 'defineProperties',
    test: function () {
      var o = {};
      var descriptors = {};
      var sEnumerable = Symbol();
      var sNotEnumerable = Symbol();
      descriptors[sEnumerable] = {
        enumerable: true,
        value: 'enumerable'
      };
      descriptors[sNotEnumerable] = {
        value: 'non-enumerable'
      };
      Object.defineProperties(o, descriptors);
      for (var k in o) throw new Error('should not show up in a for/in');
      if (Object.keys(o).length) throw new Error('should not show up in Object.keys');
      wru.assert('property is enumerable', o.propertyIsEnumerable(sEnumerable));
      wru.assert('property is NOT enumerable', !o.propertyIsEnumerable(sNotEnumerable));
      wru.assert('enumerable property is the right one', o[sEnumerable] === 'enumerable');
      wru.assert('non enumerable property is the right one', o[sNotEnumerable] === 'non-enumerable');
    }
  }, {
    name: 'defineProperties with non enumerable properties',
    test: function () {
      var o = {};
      var s = Symbol();
      var descriptors = Object.defineProperty({}, s, {enumerable: false, value: 'would throw'});
      Object.defineProperties(o, descriptors);
      wru.assert('should not have been assigned', o[s] === undefined);
    }
  }, {
    name: 'Array.prototype[Symbol.iterator]',
    test: function () {
      var iterator = [1,2,3][Symbol.iterator]();
      var list = [];
      var result;
      while (!(result = iterator.next()).done) {
        list.push(result.value);
      }
      wru.assert('Array is iterable', list.join(',') === '1,2,3');
      for (var k in []) throw new Error('there should be no iterator here');
    }
  }, {
    name: 'String.prototype[Symbol.iterator]',
    test: function () {
      var iterator = '😺😲'[Symbol.iterator]();
      var list = [];
      var result;
      while (!(result = iterator.next()).done) {
        list.push(result.value);
      }
      wru.assert('String is iterable', list.join(',') === '😺,😲');
      for (var k in '') throw new Error('there should be no iterator here');
    }
  }, {
    name: 'getOwnPropertyDescriptor',
    test: function () {
      var s = Symbol('gOPD');
      var a = {};
      var b = {};
      a[s] = true;
      Object.defineProperty(b, s, {value: false});
      wru.assert('a has it enumerable', a.propertyIsEnumerable(s));
      wru.assert('b has NOT it enumerable', !b.propertyIsEnumerable(s));
      wru.assert('a has descriptor enumerable', Object.getOwnPropertyDescriptor(a, s).enumerable);
      wru.assert('b has NOT descriptor enumerable', !Object.getOwnPropertyDescriptor(b, s).enumerable);
    }
  }, {
    name: 'Object.create(proto, symbols)',
    test: function () {
      var s = Symbol('Object.create');
      var a = {};
      var descriptors = {test: {value: 'value'}};
      descriptors[s] = {value: 'symbol'};
      var b = Object.create(a, descriptors);
      wru.assert('create works', a.isPrototypeOf(b));
      wru.assert('descriptors work', b.hasOwnProperty('test'));
      wru.assert('descriptors symbols work', b.hasOwnProperty(s));
      wru.assert('for real', b[s] === 'symbol');
    }
  }, {
    name: 'typeof is not string',
    test: function () {
      wru.assert(typeof Symbol('typeof') !== 'string');
    }
  }, {
    name: 'instanceof is not Symbol',
    test: function () {
      wru.assert(!(Symbol('instanceof') instanceof Symbol));
    }
  }, {
    name: 'Symbol().constructor',
    test: function () {
      wru.assert(Symbol('constructor').constructor === Symbol);
    }
  }, {
    name: 'Object.create is not compromised',
    test: function () {
      var o = {};
      wru.assert(o.isPrototypeOf(Object.create(o)));
    }
  }
]);
<|endoftext|>


<commit_before>const app = require('../../server');
const constants = require('../../app/lib/constants');
const chai = require('chai');
const chaiHttp = require('chai-http');
const cheerio = require('cheerio');

const expect = chai.expect;

chai.use(chaiHttp);

function expectOpeningTimes($, rows, times) {
  expect($(rows[0]).text()).to.include('Monday');
  expect($(rows[0]).text()).to.include(times[0]);
  expect($(rows[1]).text()).to.include('Tuesday');
  expect($(rows[1]).text()).to.include(times[1]);
  expect($(rows[2]).text()).to.include('Wednesday');
  expect($(rows[2]).text()).to.include(times[2]);
  expect($(rows[3]).text()).to.include('Thursday');
  expect($(rows[3]).text()).to.include(times[3]);
  expect($(rows[4]).text()).to.include('Friday');
  expect($(rows[4]).text()).to.include(times[4]);
  expect($(rows[5]).text()).to.include('Saturday');
  expect($(rows[5]).text()).to.include(times[5]);
  expect($(rows[6]).text()).to.include('Sunday');
  expect($(rows[6]).text()).to.include(times[6]);
}

describe('app', () => {
  describe('opening times', () => {
    it('should return reception and surgery opening times', (done) => {
      chai.request(app)
        .get(`${constants.SITE_ROOT}/43213`)
        .end((err, res) => {
          expect(err).to.equal(null);
          expect(res).to.have.status(200);

          expect(res.text).to.include('Reception opening times');
          expect(res.text).to.include('When GPs see patients');
          const $ = cheerio.load(res.text);

          const receptionRows = $('table.opening-times').first().find('tr');
          const expectedRecTimes = [
            '8am to 6:30pm', '8am to 6:30pm', '8am to 1pm',
            '8am to 6:30pm', '8am to 6:30pm', 'Closed', 'Closed'];
          expectOpeningTimes($, receptionRows, expectedRecTimes);

          const surgeryRows = $('table.opening-times').last().find('tr');
          const expectedSurTimes = [
            '8am to 6:30pm', '8am to 6:30pm', '8am to 1pm',
            '8am to 6:30pm', '8am to 6:30pm', 'Closed', 'Closed'];
          expectOpeningTimes($, surgeryRows, expectedSurTimes);

          done();
        });
    });

    it('should return call reception message for no reception or surgery opening times', (done) => {
      chai.request(app)
        .get(`${constants.SITE_ROOT}/107891`)
        .end((err, res) => {
          expect(err).to.equal(null);
          expect(res).to.have.status(200);

          const $ = cheerio.load(res.text);

          expect($('h2.opening-times').first().text().trim()).to.include('Reception opening times');
          expect($('h2.opening-times').last().text().trim()).to.include('When GPs see patients');

          const receptionTableText = $('p.opening-times').first().text().trim();
          expect(receptionTableText).to.include('No information available. ' +
                                                'Contact reception to find out opening times');

          const surgeryTableText = $('p.opening-times').last().text().trim();
          expect(surgeryTableText).to.include('We don\'t have opening times for this surgery. ' +
                                              'Contact reception to find out when the surgery is open.');

          done();
        });
    });
  });
});
<commit_msg>:hammer: make tests more specific<commit_after>const app = require('../../server');
const constants = require('../../app/lib/constants');
const chai = require('chai');
const chaiHttp = require('chai-http');
const cheerio = require('cheerio');

const expect = chai.expect;

chai.use(chaiHttp);

function expectOpeningTimes($, rows, times) {
  expect($(rows[0]).text()).to.include('Monday');
  expect($(rows[0]).text()).to.include(times[0]);
  expect($(rows[1]).text()).to.include('Tuesday');
  expect($(rows[1]).text()).to.include(times[1]);
  expect($(rows[2]).text()).to.include('Wednesday');
  expect($(rows[2]).text()).to.include(times[2]);
  expect($(rows[3]).text()).to.include('Thursday');
  expect($(rows[3]).text()).to.include(times[3]);
  expect($(rows[4]).text()).to.include('Friday');
  expect($(rows[4]).text()).to.include(times[4]);
  expect($(rows[5]).text()).to.include('Saturday');
  expect($(rows[5]).text()).to.include(times[5]);
  expect($(rows[6]).text()).to.include('Sunday');
  expect($(rows[6]).text()).to.include(times[6]);
}

describe('app', () => {
  describe('opening times', () => {
    it('should return reception and surgery opening times', (done) => {
      chai.request(app)
        .get(`${constants.SITE_ROOT}/43213`)
        .end((err, res) => {
          expect(err).to.equal(null);
          expect(res).to.have.status(200);

          expect(res.text).to.include('Reception opening times');
          expect(res.text).to.include('When GPs see patients');
          const $ = cheerio.load(res.text);

          const receptionRows = $('table.opening-times').first().find('tr');
          const expectedRecTimes = [
            '8am to 6:30pm', '8am to 6:30pm', '8am to 1pm',
            '8am to 6:30pm', '8am to 6:30pm', 'Closed', 'Closed'];
          expectOpeningTimes($, receptionRows, expectedRecTimes);

          const surgeryRows = $('table.opening-times').last().find('tr');
          const expectedSurTimes = [
            '8am to 6:30pm', '8am to 6:30pm', '8am to 1pm',
            '8am to 6:30pm', '8am to 6:30pm', 'Closed', 'Closed'];
          expectOpeningTimes($, surgeryRows, expectedSurTimes);

          done();
        });
    });

    it('should return call reception message for no reception or surgery opening times', (done) => {
      chai.request(app)
        .get(`${constants.SITE_ROOT}/107891`)
        .end((err, res) => {
          expect(err).to.equal(null);
          expect(res).to.have.status(200);

          const $ = cheerio.load(res.text);

          expect($('h2.opening-times').first().text().trim()).to.equal('Reception opening times');
          expect($('h2.opening-times').last().text().trim()).to.equal('When GPs see patients');

          const receptionTableText = $('p.opening-times').first().text().trim();
          expect(receptionTableText).to.equal('No information available. ' +
                                                'Contact reception to find out opening times.');

          const surgeryTableText = $('p.opening-times').last().text().trim();
          expect(surgeryTableText).to.equal('We don\'t have opening times for this surgery. ' +
                                              'Contact reception to find out when the surgery is open.');

          done();
        });
    });
  });
});
<|endoftext|>