<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const ccxt = require ('ccxt');
const { ArrayCache, ArrayCacheBySymbolById } = require ('./base/Cache');

//  ---------------------------------------------------------------------------

module.exports = class gopax extends ccxt.gopax {
    describe () {
        return this.deepExtend (super.describe (), {
            'has': {
                'ws': true,
                'watchOrderBook': true,
                'watchMyTrades': true,
                'watchBalance': true,
                'watchOrders': true,
            },
            'urls': {
                'api': {
                    'ws': 'wss://wsapi.gopax.co.kr',
                },
            },
            'options': {
                'tradesLimit': 1000,
                'ordersLimit': 1000,
                'OHLCVLimit': 1000,
            },
        });
    }

    getSignedUrl () {
        const options = this.safeValue (this.options, 'ws', {});
        if ('url' in options) {
            return options['url'];
        }
        this.checkRequiredCredentials ();
        const nonce = this.nonce ().toString ();
        const auth = 't' + nonce;
        const rawSecret = this.base64ToBinary (this.secret);
        const signature = this.hmac (this.encode (auth), rawSecret, 'sha512', 'base64');
        const query = {
            'apiKey': this.apiKey,
            'timestamp': nonce,
            'signature': signature,
        };
        const url = this.urls['api']['ws'] + '?' + this.urlencode (query);
        options['url'] = url;
        this.options['ws'] = options;
        return url;
    }

    async watchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const name = 'orderbook';
        const messageHash = name + ':' + market['id'];
        const url = this.getSignedUrl ();
        const request = {
            'n': 'SubscribeToOrderBook',
            'o': {
                'tradingPairName': market['id'],
            },
        };
        const subscription = {
            'messageHash': messageHash,
            'name': name,
            'symbol': symbol,
            'marketId': market['id'],
            'method': this.handleOrderBook,
            'limit': limit,
            'params': params,
        };
        const message = this.extend (request, params);
        const orderbook = await this.watch (url, messageHash, message, messageHash, subscription);
        return orderbook.limit (limit);
    }

    handleDelta (orderbook, bookside, delta) {
        //
        //     {
        //         entryId: 60949856,
        //         price: 31575000,
        //         volume: 0.3163,
        //         updatedAt: 1609420344.174
        //     }
        //
        const entryId = this.safeInteger (delta, 'entryId');
        if ((orderbook['nonce'] !== undefined) && (entryId >= orderbook['nonce'])) {
            const price = this.safeFloat (delta, 'price');
            const amount = this.safeFloat (delta, 'volume');
            bookside.store (price, amount);
        }
        return entryId;
    }

    handleDeltas (orderbook, bookside, deltas) {
        let nonce = 0;
        for (let i = 0; i < deltas.length; i++) {
            const n = this.handleDelta (orderbook, bookside, deltas[i]);
            nonce = Math.max (nonce, n);
        }
        return nonce;
    }

    handleOrderBookMessage (client, message, orderbook) {
        //
        //     {
        //         i: -1,
        //         n: 'OrderBookEvent',
        //         o: {
        //             ask: [
        //                 { entryId: 60949856, price: 31575000, volume: 0.3163, updatedAt: 1609420344.174 }
        //             ],
        //             bid: [],
        //             tradingPairName: 'BTC-KRW'
        //         }
        //     }
        //
        const o = this.safeValue (message, 'o', {});
        const askNonce = this.handleDeltas (orderbook, orderbook['asks'], this.safeValue (o, 'ask', []));
        const bidNonce = this.handleDeltas (orderbook, orderbook['bids'], this.safeValue (o, 'bid', []));
        const nonce = Math.max (askNonce, bidNonce);
        orderbook['nonce'] = nonce;
        return orderbook;
    }

    handleOrderBook (client, message) {
        //
        // initial snapshot
        //
        //     {
        //         n: 'SubscribeToOrderBook',
        //         o: {
        //             ask: [
        //                 { entryId: 60490601, price: 32061000, volume: 0.09996, updatedAt: 1609412729.325 },
        //                 { entryId: 60490959, price: 32078000, volume: 0.206, updatedAt: 1609412735.793 },
        //                 { entryId: 60490687, price: 32085000, volume: 0.192, updatedAt: 1609412730.373 },
        //             ],
        //             bid: [
        //                 { entryId: 60491143, price: 32059000, volume: 0.3118, updatedAt: 1609412740.011 },
        //                 { entryId: 60490948, price: 32058000, volume: 0.00162449, updatedAt: 1609412735.555 },
        //                 { entryId: 60488158, price: 32053000, volume: 0.206, updatedAt: 1609412680.169 },
        //             ],
        //             tradingPairName: 'BTC-KRW',
        //             maxEntryId: 60491355
        //         }
        //     }
        //
        // delta update
        //
        //     {
        //         i: -1,
        //         n: 'OrderBookEvent',
        //         o: {
        //             ask: [
        //                 { entryId: 60949856, price: 31575000, volume: 0.3163, updatedAt: 1609420344.174 }
        //             ],
        //             bid: [],
        //             tradingPairName: 'BTC-KRW'
        //         }
        //     }
        //
        const n = this.safeString (message, 'n');
        const o = this.safeValue (message, 'o');
        const marketId = this.safeString (o, 'tradingPairName');
        const market = this.safeMarket (marketId, undefined, '-');
        const symbol = market['symbol'];
        // const nonce = this.safeInteger (o, 'maxEntryId');
        const name = 'orderbook';
        const messageHash = name + ':' + market['id'];
        const subscription = this.safeValue (client.subscriptions, messageHash, {});
        const limit = this.safeInteger (subscription, 'limit');
        if (!(symbol in this.orderbooks)) {
            this.orderbooks[symbol] = this.orderBook ({}, limit);
        }
        const orderbook = this.safeValue (this.orderbooks, symbol);
        if (n === 'SubscribeToOrderBook') {
            orderbook['nonce'] = 0;
            this.handleOrderBookMessage (client, message, orderbook);
            for (let i = 0; i < orderbook.cache.length; i++) {
                const message = orderbook.cache[i];
                this.handleOrderBookMessage (client, message, orderbook);
            }
            client.resolve (orderbook, messageHash);
        } else {
            if (orderbook['nonce'] === undefined) {
                orderbook.cache.push (message);
            } else {
                this.handleOrderBookMessage (client, message, orderbook);
                client.resolve (orderbook, messageHash);
            }
        }
    }

    async watchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const name = 'orders';
        const subscriptionHash = name;
        let messageHash = name;
        if (symbol !== undefined) {
            messageHash += ':' + symbol;
        }
        const url = this.getSignedUrl ();
        const request = {
            'n': 'SubscribeToOrders',
            'o': {},
        };
        const subscription = {
            'messageHash': messageHash,
            'name': name,
            'symbol': symbol,
            'method': this.handleOrderBook,
            'limit': limit,
            'params': params,
        };
        const message = this.extend (request, params);
        const orders = await this.watch (url, messageHash, message, subscriptionHash, subscription);
        if (this.newUpdates) {
            limit = orders.getLimit (symbol, limit);
        }
        return this.filterBySymbolSinceLimit (orders, symbol, since, limit, true);
    }

    parseWsOrderStatus (status) {
        const statuses = {
            '1': 'open',
            '2': 'canceled',
            '3': 'closed',
            '4': 'open',
            // '5': 'reserved',
        };
        return this.safeString (statuses, status, status);
    }

    parseWsOrderType (orderType) {
        const types = {
            '1': 'limit',
            '2': 'market',
        };
        return this.safeString (types, orderType, orderType);
    }

    parseWsOrderSide (side) {
        const sides = {
            '1': 'buy',
            '2': 'sell',
        };
        return this.safeString (sides, side, side);
    }

    parseWsTimeInForce (timeInForce) {
        const timeInForces = {
            '0': 'GTC',
            '1': 'PO',
            '2': 'IOC',
            '3': 'FOK',
        };
        return this.safeString (timeInForces, timeInForce, timeInForce);
    }

    parseWsOrder (order, market = undefined) {
        //
        //     {
        //         "orderId": 327347,           // order ID
        //         "status": 1,                 // 1(not filled), 2(canceled), 3(completely filled), 4(partially filled), 5(reserved)
        //         "side": 2,                   // 1(bid), 2(ask)
        //         "type": 1,                   // 1(limit), 2(market)
        //         "price": 5500000,            // price
        //         "orgAmount": 1,              // initially placed amount
        //         "remainAmount": 1,           // unfilled or remaining amount
        //         "createdAt": 1597218137,     // placement time
        //         "updatedAt": 1597218137,     // last update time
        //         "tradedBaseAmount": 0,       // filled base asset amount (in ZEC for this case)
        //         "tradedQuoteAmount": 0,      // filled quote asset amount (in KRW for this case)
        //         "feeAmount": 0,              // fee amount
        //         "rewardAmount": 0,           // reward amount
        //         "timeInForce": 0,            // 0(gtc), 1(post only), 2(ioc), 3(fok)
        //         "protection": 1,             // 1(not applied), 2(applied)
        //         "forcedCompletionReason": 0, // 0(n/a), 1(timeInForce), 2(protection)
        //         "stopPrice": 0,              // stop price (> 0 only for stop orders)
        //         "takerFeeAmount": 0,         // fee amount paid as a taker position
        //         "tradingPairName": "ZEC-KRW" // order book
        //     }
        //
        const id = this.safeString (order, 'orderId');
        const clientOrderId = this.safeString (order, 'clientOrderId');
        const timestamp = this.safeTimestamp (order, 'createdAt');
        const type = this.parseWsOrderType (this.safeString (order, 'type'));
        const side = this.parseWsOrderSide (this.safeString (order, 'side'));
        const timeInForce = this.parseWsTimeInForce (this.safeString (order, 'timeInForce'));
        const price = this.safeFloat (order, 'price');
        const amount = this.safeFloat (order, 'orgAmount');
        const stopPrice = this.safeFloat (order, 'stopPrice');
        const remaining = this.safeFloat (order, 'remainAmount');
        const marketId = this.safeString (order, 'tradingPairName');
        market = this.safeMarket (marketId, market, '-');
        const status = this.parseWsOrderStatus (this.safeString (order, 'status'));
        let filled = this.safeFloat (order, 'tradedBaseAmount');
        let cost = this.safeFloat (order, 'tradedQuoteAmount');
        let updated = undefined;
        if ((amount !== undefined) && (remaining !== undefined)) {
            filled = Math.max (0, amount - remaining);
            if (filled > 0) {
                updated = this.safeTimestamp (order, 'updatedAt');
            }
            if (price !== undefined) {
                cost = filled * price;
            }
        }
        let postOnly = undefined;
        if (timeInForce !== undefined) {
            postOnly = (timeInForce === 'PO');
        }
        const fee = undefined;
        return {
            'id': id,
            'clientOrderId': clientOrderId,
            'datetime': this.iso8601 (timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': updated,
            'status': status,
            'symbol': market['symbol'],
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'average': price,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'cost': cost,
            'trades': undefined,
            'fee': fee,
            'info': order,
        };
    }

    handleOrder (client, message, order, market = undefined) {
        const parsed = this.parseWsOrder (order);
        if (this.orders === undefined) {
            const limit = this.safeInteger (this.options, 'ordersLimit', 1000);
            this.orders = new ArrayCacheBySymbolById (limit);
        }
        const orders = this.orders;
        orders.append (parsed);
        return parsed;
    }

    handleOrders (client, message) {
        //
        // subscription response
        //
        //
        //     {
        //         "n": "SubscribeToOrders",
        //         "o": {
        //             "data": [
        //                 {
        //                     "orderId": 327347,           // order ID
        //                     "status": 1,                 // 1(not filled), 2(canceled), 3(completely filled), 4(partially filled), 5(reserved)
        //                     "side": 2,                   // 1(bid), 2(ask)
        //                     "type": 1,                   // 1(limit), 2(market)
        //                     "price": 5500000,            // price
        //                     "orgAmount": 1,              // initially placed amount
        //                     "remainAmount": 1,           // unfilled or remaining amount
        //                     "createdAt": 1597218137,     // placement time
        //                     "updatedAt": 1597218137,     // last update time
        //                     "tradedBaseAmount": 0,       // filled base asset amount (in ZEC for this case)
        //                     "tradedQuoteAmount": 0,      // filled quote asset amount (in KRW for this case)
        //                     "feeAmount": 0,              // fee amount
        //                     "rewardAmount": 0,           // reward amount
        //                     "timeInForce": 0,            // 0(gtc), 1(post only), 2(ioc), 3(fok)
        //                     "protection": 1,             // 1(not applied), 2(applied)
        //                     "forcedCompletionReason": 0, // 0(n/a), 1(timeInForce), 2(protection)
        //                     "stopPrice": 0,              // stop price (> 0 only for stop orders)
        //                     "takerFeeAmount": 0,         // fee amount paid as a taker position
        //                     "tradingPairName": "ZEC-KRW" // order book
        //                 }
        //             ]
        //         }
        //     }
        //
        // delta update
        //
        //     {
        //         "i": -1,                         // always -1 in case of delta push
        //         "n": "OrderEvent",
        //         "o": {
        //             "orderId": 327347,
        //             "status": 4,                 // changed to 4(partially filled)
        //             "side": 2,
        //             "type": 1,
        //             "price": 5500000,
        //             "orgAmount": 1,
        //             "remainAmount": 0.8,         // -0.2 as 0.2 ZEC is filled
        //             "createdAt": 1597218137,
        //             "updatedAt": 1599093631,     // updated
        //             "tradedBaseAmount": 0.2,     // 0.2 ZEC goes out
        //             "tradedQuoteAmount": 1100000,// 1,100,000 KRW comes in
        //             "feeAmount": 440,            // fee amount (in KRW and 0.04% for this case)
        //             "rewardAmount": 0,
        //             "timeInForce": 0,
        //             "protection": 1,
        //             "forcedCompletionReason": 0,
        //             "stopPrice": 0,
        //             "takerFeeAmount": 0,
        //             "tradingPairName": "ZEC-KRW"
        //         }
        //     }
        //
        const o = this.safeValue (message, 'o', []);
        const data = this.safeValue (o, 'data');
        const messageHash = 'orders';
        if (data === undefined) {
            // single order delta update
            const order = this.handleOrder (client, message, data);
            const symbol = order['symbol'];
            client.resolve (this.orders, messageHash);
            client.resolve (this.orders, messageHash + ':' + symbol);
        } else {
            // initial subscription response with multiple orders
            const dataLength = data.length;
            if (dataLength > 0) {
                const symbols = {};
                for (let i = 0; i < dataLength; i++) {
                    const order = this.handleOrder (client, message, data[i]);
                    const symbol = order['symbol'];
                    symbols[symbol] = true;
                }
                client.resolve (this.orders, messageHash);
                const keys = Object.keys (symbols);
                for (let i = 0; i < keys.length; i++) {
                    const symbol = keys[i];
                    client.resolve (this.orders, messageHash + ':' + symbol);
                }
            }
        }
    }

    async watchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const name = 'myTrades';
        const subscriptionHash = name;
        let messageHash = name;
        if (symbol !== undefined) {
            messageHash += ':' + symbol;
        }
        const url = this.getSignedUrl ();
        const request = {
            'n': 'SubscribeToTrades',
            'o': {},
        };
        const subscription = {
            'messageHash': messageHash,
            'name': name,
            'symbol': symbol,
            'method': this.handleMyTrades,
            'limit': limit,
            'params': params,
        };
        const message = this.extend (request, params);
        const trades = await this.watch (url, messageHash, message, subscriptionHash, subscription);
        if (this.newUpdates) {
            limit = trades.getLimit (symbol, limit);
        }
        return this.filterBySinceLimit (trades, since, limit, 'timestamp', true);
    }

    handleMyTrades (client, message) {
        //
        // subscription response
        //
        //     { n: 'SubscribeToTrades', o: {} }
        //
        //  regular update
        //
        //     {
        //         "i": -1,
        //         "n": "TradeEvent",
        //         "o": {
        //             "tradeId": 74072,            // trade ID
        //             "orderId": 453529,           // order ID
        //             "side": 2,                   // 1(bid), 2(ask)
        //             "type": 1,                   // 1(limit), 2(market)
        //             "baseAmount": 0.01,          // filled base asset amount (in ZEC for this case)
        //             "quoteAmount": 1,            // filled quote asset amount (in KRW for this case)
        //             "fee": 0.0004,               // fee
        //             "price": 100,                // price
        //             "isSelfTrade": false,        // whether both of matching orders are yours
        //             "occurredAt": 1603932107,    // trade occurrence time
        //             "tradingPairName": "ZEC-KRW" // order book
        //         }
        //     }
        //
        const o = this.safeValue (message, 'o', {});
        const name = 'myTrades';
        const messageHash = name;
        const trade = this.parseTrade (o);
        const symbol = trade['symbol'];
        let array = this.safeValue (this.myTrades, symbol);
        if (array === undefined) {
            const limit = this.safeInteger (this.options, 'tradesLimit', 1000);
            array = new ArrayCache (limit);
        }
        array.append (trade);
        this.myTrades[symbol] = array;
        client.resolve (array, messageHash);
        client.resolve (array, messageHash + ':' + symbol);
    }

    async watchBalance (params = {}) {
        await this.loadMarkets ();
        const name = 'balance';
        const messageHash = name;
        const url = this.getSignedUrl ();
        const request = {
            'n': 'SubscribeToBalances',
            'o': {},
        };
        const subscription = {
            'messageHash': messageHash,
            'name': name,
            'params': params,
        };
        const message = this.extend (request, params);
        return await this.watch (url, messageHash, message, messageHash, subscription);
    }

    handleBalance (client, message) {
        //
        //     {
        //         n: 'SubscribeToBalances',
        //         o: {
        //             result: true,
        //             data: [
        //                 { assetId: 1, avail: 30000.74103433, hold: 0, pendingWithdrawal: 0, blendedPrice: 1, lastUpdatedAt: 1609519939.412, isoAlpha3: 'KRW' },
        //                 { assetId: 3, avail: 0, hold: 0, pendingWithdrawal: 0, blendedPrice: 0, lastUpdatedAt: 0, isoAlpha3: 'ETH' },
        //                 { assetId: 4, avail: 0, hold: 0, pendingWithdrawal: 0, blendedPrice: 0, lastUpdatedAt: 0, isoAlpha3: 'BTC' },
        //             ],
        //         },
        //     }
        //
        //     {
        //         "i": -1,                             // always -1 in case of delta push
        //         "n": "BalanceEvent",
        //         "o": {
        //             "assetId": 7,
        //             "avail": 990.4998,               // +1 as you can use 1 ZEC more to place a new order
        //             "hold": 1,                       // -1 as you take it back from an order book
        //             "pendingWithdrawal": 0,
        //             "blendedPrice": 429413.08986192,
        //             "lastUpdatedAt": 1599098077.27,
        //             "isoAlpha3": "ZEC"
        //         }
        //     }
        //
        const o = this.safeValue (message, 'o');
        const data = this.safeValue (o, 'data');
        if (data === undefined) {
            const balance = this.parseBalanceResponse ([ o ]);
            this.balance = this.parseBalance (this.extend (this.balance, balance));
        } else {
            this.balance = this.parseBalanceResponse (data);
        }
        const messageHash = 'balance';
        client.resolve (this.balance, messageHash);
    }

    async pong (client, message) {
        //
        //     "primus::ping::1609504526621"
        //
        const messageString = JSON.parse (message);
        const parts = messageString.split ('::');
        const requestId = this.safeString (parts, 2);
        const response = 'primus::pong::' + requestId;
        await client.send (response);
    }

    handlePing (client, message) {
        this.spawn (this.pong, client, message);
    }

    handleMessage (client, message) {
        //
        // ping string message
        //
        //     "primus::ping::1609504526621"
        //
        // regular json message
        //
        //     {
        //         n: 'SubscribeToOrderBook',
        //         o: {
        //             ask: [
        //                 { entryId: 60490601, price: 32061000, volume: 0.09996, updatedAt: 1609412729.325 },
        //                 { entryId: 60490959, price: 32078000, volume: 0.206, updatedAt: 1609412735.793 },
        //                 { entryId: 60490687, price: 32085000, volume: 0.192, updatedAt: 1609412730.373 },
        //             ],
        //             bid: [
        //                 { entryId: 60491143, price: 32059000, volume: 0.3118, updatedAt: 1609412740.011 },
        //                 { entryId: 60490948, price: 32058000, volume: 0.00162449, updatedAt: 1609412735.555 },
        //                 { entryId: 60488158, price: 32053000, volume: 0.206, updatedAt: 1609412680.169 },
        //             ],
        //             tradingPairName: 'BTC-KRW',
        //             maxEntryId: 60491355
        //         }
        //     }
        //
        if (typeof message === 'string') {
            this.handlePing (client, message);
        } else {
            const methods = {
                'OrderBookEvent': this.handleOrderBook,
                'SubscribeToOrderBook': this.handleOrderBook,
                // 'SubscribeToTrades': this.handleMyTrades,
                'TradeEvent': this.handleMyTrades,
                'SubscribeToOrders': this.handleOrders,
                'OrderEvent': this.handleOrders,
                'SubscribeToBalances': this.handleBalance,
                'BalanceEvent': this.handleBalance,
            };
            const n = this.safeString (message, 'n');
            const method = this.safeValue (methods, n);
            if (method !== undefined) {
                return method.call (this, client, message);
            }
        }
        return message;
    }
};
<commit_msg>gopax shutdown<commit_after><|endoftext|>


<commit_before>//Presentational (dumb) component

//declared as stateles function
//no need for this keyword, prevents from using react lifecycle methods
//example of ES2015 default parameter and template strings
//doesn't have to alwas receive props: const PresentationalComponent = () => (...);

import React, { PropTypes } from 'react';

//components
import Wrapper from './Wrapper';
import CounterInfo from './CounterInfo';
import Description from './Description';
import CounterValue from './CounterValue';
import CounterButton from './CounterButton';

const CounterPanel = ({counter, counterStep, onIncrease, onDecrease, lowerLimit}) => (
  // disable CounterButton if counter value is lower than limit
  <Wrapper>
    <CounterInfo>
      <Description>Current Counter Value</Description><CounterValue>{counter}</CounterValue>
    </CounterInfo>
    <CounterButton onClick={onIncrease}>Increase Value By {counterStep}</CounterButton>
    {
      counter > lowerLimit ?
      <CounterButton onClick={onDecrease}>Decrease Value By {counterStep}</CounterButton> :
      <CounterButton disabled>Decrease Value By {counterStep}</CounterButton>
    }
  </Wrapper>
);

//list of react PropTypes: https://facebook.github.io/react/docs/typechecking-with-proptypes.html
//you can pass strings, numbers, objects, arrays, etc.
CounterPanel.propTypes = {
  counter: PropTypes.number.isRequired,
  counterStep: PropTypes.number.isRequired,
  lowerLimit: PropTypes.number.isRequired,
  onIncrease: PropTypes.func.isRequired,
  onDecrease: PropTypes.func.isRequired
};

export default CounterPanel;
<commit_msg>CounterPanel propTypes refactored<commit_after>//Presentational (dumb) component

//declared as stateles function
//no need for this keyword, prevents from using react lifecycle methods
//example of ES2015 default parameter and template strings
//doesn't have to alwas receive props: const PresentationalComponent = () => (...);

import React, { PropTypes } from 'react';

//components
import Wrapper from './Wrapper';
import CounterInfo from './CounterInfo';
import Description from './Description';
import CounterValue from './CounterValue';
import CounterButton from './CounterButton';

const CounterPanel = ({counter, counterStep, onIncrease, onDecrease, lowerLimit}) => (
  // disable CounterButton if counter value is lower than limit
  <Wrapper>
    <CounterInfo>
      <Description>Current Counter Value</Description><CounterValue>{counter}</CounterValue>
    </CounterInfo>
    <CounterButton onClick={onIncrease}>Increase Value By {counterStep}</CounterButton>
    {
      counter > lowerLimit ?
      <CounterButton onClick={onDecrease}>Decrease Value By {counterStep}</CounterButton> :
      <CounterButton disabled>Decrease Value By {counterStep}</CounterButton>
    }
  </Wrapper>
);

//list of react PropTypes: https://facebook.github.io/react/docs/typechecking-with-proptypes.html
//you can pass strings, numbers, objects, arrays, etc.
CounterPanel.propTypes = {
  counter: PropTypes.number.isRequired,
  counterStep: PropTypes.number.isRequired,
  lowerLimit: PropTypes.number.isRequired,
  onIncrease: PropTypes.func,
  onDecrease: PropTypes.func
};

export default CounterPanel;
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */
sap.ui.define(['jquery.sap.global'],
	function(jQuery) {
	"use strict";

	/**
	 * Panel renderer
	 * @namespace
	 */
	var PanelRenderer = {};

	/**
	 * Renders the HTML for the given control, using the provided {@link sap.ui.core.RenderManager}.
	 *
	 * @param {sap.ui.core.RenderManager}
	 *          oRm the RenderManager that can be used for writing to the render output buffer
	 * @param {sap.ui.core.Control}
	 *          oControl an object representation of the control that should be rendered
	 */
	PanelRenderer.render = function(oRm, oControl) {
		this.startPanel(oRm, oControl);

		this.renderHeader(oRm, oControl);

		this.renderContent(oRm, oControl);

		this.endPanel(oRm);
	};

	PanelRenderer.startPanel = function (oRm, oControl) {
		oRm.write("
");
	};

	PanelRenderer.renderHeader = function (oRm, oControl) {
		var bIsExpandable = oControl.getExpandable(),
			oHeaderTBar = oControl.getHeaderToolbar();

		if (bIsExpandable) {

			// we need a wrapping div around icon and header since otherwise the border needed for both do not exact align
			oRm.write("
");

			var oIcon = oControl._getIcon();
			if (oControl.getExpanded()) {
				oIcon.addStyleClass("sapMPanelExpandableIconExpanded");
			} else {
				oIcon.removeStyleClass("sapMPanelExpandableIconExpanded");
			}

			oRm.renderControl(oIcon);
		}

		// render header
		var sHeaderText = oControl.getHeaderText();

		if (oHeaderTBar) {
			oHeaderTBar.setDesign(sap.m.ToolbarDesign.Transparent, true);

			if (bIsExpandable) {
				// use this class as marker class - to ease selection later in onAfterRendering
				oHeaderTBar.addStyleClass("sapMPanelHdrExpandable");
			}

			oRm.renderControl(oHeaderTBar);

		} else if (sHeaderText) {
			oRm.write("
");

			oRm.writeEscaped(sHeaderText);
			oRm.write("
");
		}

		if (bIsExpandable) {
			oRm.write("
");
		}

		var oInfoTBar = oControl.getInfoToolbar();

		if (oInfoTBar) {
			if (bIsExpandable) {
				// use this class as marker class to ease selection later in onAfterRendering
				oInfoTBar.addStyleClass("sapMPanelExpandablePart");
			}

			// render infoBar
			oInfoTBar.setDesign(sap.m.ToolbarDesign.Info, true);
			oRm.renderControl(oInfoTBar);
		}
	};

	PanelRenderer.renderContent = function (oRm, oControl) {
		this.startContent(oRm, oControl);

		this.renderChildren(oRm, oControl.getContent());

		this.endContent(oRm);
	};

	PanelRenderer.startContent = function (oRm, oControl) {
		oRm.write("
");
	};

	PanelRenderer.renderChildren = function (oRm, aChildren) {
		aChildren.forEach(oRm.renderControl);
	};

	PanelRenderer.endContent = function (oRm) {
		oRm.write("
");
	};

	PanelRenderer.endPanel = function (oRm) {
		oRm.write("
");
	};

	return PanelRenderer;

}, /* bExport= */ true);
<commit_msg>[FIX] sap.m.Panel: Header is now shown even without text specified<commit_after>/*!
 * ${copyright}
 */
sap.ui.define(['jquery.sap.global'],
	function(jQuery) {
	"use strict";

	/**
	 * Panel renderer
	 * @namespace
	 */
	var PanelRenderer = {};

	/**
	 * Renders the HTML for the given control, using the provided {@link sap.ui.core.RenderManager}.
	 *
	 * @param {sap.ui.core.RenderManager}
	 *          oRm the RenderManager that can be used for writing to the render output buffer
	 * @param {sap.ui.core.Control}
	 *          oControl an object representation of the control that should be rendered
	 */
	PanelRenderer.render = function(oRm, oControl) {
		this.startPanel(oRm, oControl);

		this.renderHeader(oRm, oControl);

		this.renderContent(oRm, oControl);

		this.endPanel(oRm);
	};

	PanelRenderer.startPanel = function (oRm, oControl) {
		oRm.write("
");
	};

	PanelRenderer.renderHeader = function (oRm, oControl) {
		var bIsExpandable = oControl.getExpandable(),
			oHeaderTBar = oControl.getHeaderToolbar();

		if (bIsExpandable) {

			// we need a wrapping div around icon and header since otherwise the border needed for both do not exact align
			oRm.write("
");

			var oIcon = oControl._getIcon();
			if (oControl.getExpanded()) {
				oIcon.addStyleClass("sapMPanelExpandableIconExpanded");
			} else {
				oIcon.removeStyleClass("sapMPanelExpandableIconExpanded");
			}

			oRm.renderControl(oIcon);
		}

		// render header
		var sHeaderText = oControl.getHeaderText();

		if (oHeaderTBar) {
			oHeaderTBar.setDesign(sap.m.ToolbarDesign.Transparent, true);

			if (bIsExpandable) {
				// use this class as marker class - to ease selection later in onAfterRendering
				oHeaderTBar.addStyleClass("sapMPanelHdrExpandable");
			}

			oRm.renderControl(oHeaderTBar);

		} else {
			oRm.write("
");

			oRm.writeEscaped(sHeaderText);
			oRm.write("
");
		}

		if (bIsExpandable) {
			oRm.write("
");
		}

		var oInfoTBar = oControl.getInfoToolbar();

		if (oInfoTBar) {
			if (bIsExpandable) {
				// use this class as marker class to ease selection later in onAfterRendering
				oInfoTBar.addStyleClass("sapMPanelExpandablePart");
			}

			// render infoBar
			oInfoTBar.setDesign(sap.m.ToolbarDesign.Info, true);
			oRm.renderControl(oInfoTBar);
		}
	};

	PanelRenderer.renderContent = function (oRm, oControl) {
		this.startContent(oRm, oControl);

		this.renderChildren(oRm, oControl.getContent());

		this.endContent(oRm);
	};

	PanelRenderer.startContent = function (oRm, oControl) {
		oRm.write("
");
	};

	PanelRenderer.renderChildren = function (oRm, aChildren) {
		aChildren.forEach(oRm.renderControl);
	};

	PanelRenderer.endContent = function (oRm) {
		oRm.write("
");
	};

	PanelRenderer.endPanel = function (oRm) {
		oRm.write("
");
	};

	return PanelRenderer;

}, /* bExport= */ true);
<|endoftext|>


<commit_before>function strongEval(str,globalObj) {
	var obj={};
	for (var index in window) {//overwrite all of the items in the global namespace (not actually changing them)
		obj[index]=undefined;
	}
	for (var index in this) {//overwrite all of the items in the global-ish namespace (not actually changing them)
		obj[index]=undefined;
	}
	for (var index in globalObj) {//copy over properties from passed object
		if (globalObj.hasOwnProperty(index)) obj[index]=globalObj[index];
	}
	str=str.replace(/{\s*$/,"").replace(/^[^{]*}/,"");
	//console.log(str);
	return (new Function("window","with(window) {"+str+"}")).call(globalObj,obj);
};
function Ai(name,obj/*opt*/) {
	/*
	There are 2 ways two run this function

		var myAi= new Ai("My AI's name");//1st
		var mySecondAi= new Ai("My 2nd AI's name",{
			//values here
		});//2nd
	*/
	if  (arguments.length==0||arguments.length>2) throw "The Ai constructor requires 1-2 values passed";
	obj=obj||{};//prevent bugs
	this.name=name||"AIR bot";
	this._defaultResp={};
	this._allresps=[];
	this.types={
		string:function(str) {return str},
	};

	for (var i in this) {
		if (typeof this[i]=="object") {
			for (var ii in obj[i]) {
				this[i][ii]=obj[i][ii];
			}
		}else this[i]=obj[i];//overwrite with obj
	}

	if (typeof this.name!="string") throw "The name for your Ai bot must be a string";
};
Ai.prototype.reactTo=function(input,name,type) {
	/*
	How to use this function:

		var myAi= new Ai("My AI's name");
		var aisOutput=myAi.reactTo("input","usersname","type of input(optional but will use the typeof operator to determine this value if not specified)");
	*/
	var output="",//set varubles
	type=type||typeof input;

	//check varubles
	if (typeof this.types[type]==="undefined") {
		throw "Invalid type";
	}else{
		input=this.types[type](input);
	}

	//apply default responce
	if (this._defaultResp.hasOwnProperty(input)) output+=this._defaultResp[input];

	//edit output(remove negetaive phrases)

	//act
	this._defaultResp[input]=output;//add as responce
	return output;//return value for further prossesing
}
<commit_msg>remove type of filter for name<commit_after>function strongEval(str,globalObj) {
	var obj={};
	for (var index in window) {//overwrite all of the items in the global namespace (not actually changing them)
		obj[index]=undefined;
	}
	for (var index in this) {//overwrite all of the items in the global-ish namespace (not actually changing them)
		obj[index]=undefined;
	}
	for (var index in globalObj) {//copy over properties from passed object
		if (globalObj.hasOwnProperty(index)) obj[index]=globalObj[index];
	}
	str=str.replace(/{\s*$/,"").replace(/^[^{]*}/,"");
	//console.log(str);
	return (new Function("window","with(window) {"+str+"}")).call(globalObj,obj);
};
function Ai(name,obj/*opt*/) {
	/*
	There are 2 ways two run this function

		var myAi= new Ai("My AI's name");//1st
		var mySecondAi= new Ai("My 2nd AI's name",{
			//values here
		});//2nd
	*/
	if  (arguments.length==0||arguments.length>2) throw "The Ai constructor requires 1-2 values passed";
	obj=obj||{};//prevent bugs
	this.name=name||"AIR bot";
	this._defaultResp={};
	this._allresps=[];
	this.types={
		string:function(str) {return str},
	};

	for (var i in this) {
		if (typeof this[i]=="object") {
			for (var ii in obj[i]) {
				this[i][ii]=obj[i][ii];
			}
		}else this[i]=obj[i];//overwrite with obj
	}

	/*if (typeof this.name!=="string") {
		throw "The name for your Ai bot must be a string";
	}*/
};
Ai.prototype.reactTo=function(input,name,type) {
	/*
	How to use this function:

		var myAi= new Ai("My AI's name");
		var aisOutput=myAi.reactTo("input","usersname","type of input(optional but will use the typeof operator to determine this value if not specified)");
	*/
	var output="",//set varubles
	type=type||typeof input;

	//check varubles
	if (typeof this.types[type]==="undefined") {
		throw "Invalid type";
	}else{
		input=this.types[type](input);
	}

	//apply default responce
	if (this._defaultResp.hasOwnProperty(input)) output+=this._defaultResp[input];

	//edit output(remove negetaive phrases)

	//act
	this._defaultResp[input]=output;//add as responce
	return output;//return value for further prossesing
}
<|endoftext|>


<commit_before>define(['base/js/namespace','jquery'], function(Jupyter, $) {
    function does_to_dos_cell_exist() {
        var notebook_cells = Jupyter.notebook.get_cells();
        for (var i = 0; i < notebook_cells.length; i++) {
            var cell = notebook_cells[i];
            if (cell.metadata.id == 'to_dos_cell') {
                return cell;
            }
        }
        return false;
    }

    function place_to_dos_cell() {
        var to_dos_cell = Jupyter.notebook.insert_cell_at_index('markdown', 0);
        to_dos_cell.set_text('# To Dos\n');
        to_dos_cell.render();
        to_dos_cell.metadata.id = 'to_dos_cell';
        return to_dos_cell;
    }

    function get_to_dos_cell() {
        var existing_cell = does_to_dos_cell_exist();
        if (existing_cell) {
            var to_dos_cell = existing_cell;
        } else {
            var to_dos_cell = place_to_dos_cell();
        }
        return to_dos_cell;
    }

    function load_to_dos_cell_events() {
        var to_dos_cell = get_to_dos_cell();
        to_dos_cell.element.click(function(event) {
            if (!$(event.target).is("input")) {
                var current_content = to_dos_cell.get_text();
                current_content += "<input type='checkbox' class='to-do-item-check'></input>";
                current_content += "<input type='text' class='to-do-item'></input>
";
                to_dos_cell.set_text(current_content);
                to_dos_cell.render();
            }
        });
    }

    function place_to_dos_button() {
        if (!Jupyter.toolbar) {
            $([Jupyter.events]).on("app_initialized.NotebookApp", place_to_dos_button);
        }

        if ($("#create-to-dos-button").length == 0) {
            Jupyter.toolbar.add_buttons_group([
                {
                    'label': 'Add To-do List Cell',
                    'icon': 'fa-list',
                    'callback': load_to_dos_cell_events,
                    'id': 'create-to-dos-button',
                }
            ]);
        }
    }

    function load_ipython_extension() {
        console.log('Loading notebook-to-dos extension');
        place_to_dos_button();
    }

    return {
        load_ipython_extension: load_ipython_extension
    };
});
<commit_msg>Use button to add new task<commit_after>define(['base/js/namespace','jquery'], function(Jupyter, $) {
    function does_to_dos_cell_exist() {
        var notebook_cells = Jupyter.notebook.get_cells();
        for (var i = 0; i < notebook_cells.length; i++) {
            var cell = notebook_cells[i];
            if (cell.metadata.id == 'to_dos_cell') {
                return cell;
            }
        }
        return false;
    }

    function place_to_dos_cell() {
        var to_dos_cell = Jupyter.notebook.insert_cell_at_index('markdown', 0);
        var cell_text = '# To Dos\n';
        cell_text += '<button class="to-dos-add-task">Add Task</button>

';
        to_dos_cell.set_text(cell_text);
        to_dos_cell.render();
        to_dos_cell.metadata.id = 'to_dos_cell';
        return to_dos_cell;
    }

    function get_to_dos_cell() {
        var existing_cell = does_to_dos_cell_exist();
        if (existing_cell) {
            var to_dos_cell = existing_cell;
        } else {
            var to_dos_cell = place_to_dos_cell();
        }
        return to_dos_cell;
    }

    function load_to_dos_cell_events() {
        var to_dos_cell = get_to_dos_cell();
        to_dos_cell.element.click(function(event) {
            if ($(event.target).is("button.to-dos-add-task")) {
                var current_content = to_dos_cell.get_text();
                current_content += "<input type='checkbox' class='to-do-item-check'></input>";
                current_content += "<input type='text' class='to-do-item'></input>
";
                to_dos_cell.set_text(current_content);
                to_dos_cell.render();
            }
        });
    }

    function place_to_dos_button() {
        if (!Jupyter.toolbar) {
            $([Jupyter.events]).on("app_initialized.NotebookApp", place_to_dos_button);
        }

        if ($("#create-to-dos-button").length == 0) {
            Jupyter.toolbar.add_buttons_group([
                {
                    'label': 'Add To-do List Cell',
                    'icon': 'fa-list',
                    'callback': load_to_dos_cell_events,
                    'id': 'create-to-dos-button',
                }
            ]);
        }
    }

    function load_ipython_extension() {
        console.log('Loading notebook-to-dos extension');
        place_to_dos_button();
    }

    return {
        load_ipython_extension: load_ipython_extension
    };
});
<|endoftext|>


<commit_before>var fmt = function (fmt_str, args) {
    // var debug = require("sys").debug;
    var debug = function (n) { };

    var SIGIL = '%',
        OPEN = '{',
        CLOSE = '}';
    var BACKSLASH = "\\";

    var out = [];
    var first = 0;

    var l = fmt_str.length;

    for (var i = 0; i < l; ++i) {
        var c0 = fmt_str[i],
            c1 = fmt_str[i + 1];
        switch (c0) {
        case SIGIL:
            // "%%" resolves to %
            if (c1 === '%') {
                out.push(fmt_str.substr(first, i + 1));
                ++i;
                first = i;
                continue;
            }

            // % followed by anything other than '{' is not allowed
            if (c1 !== OPEN) {
                throw "% must be followed by either '%' or '{'";
            }

            var escaped = false;
            // scan for the closing '}'
            for (var j = i + 2; j < l; ++j) {
                if (fmt_str[j] === CLOSE) {
                    // debug("current char: " + fmt_str[j]);
                    // debug("penultimate char: " + fmt_str[j - 1]);
                    if (fmt_str[j - 1] === BACKSLASH) {
                        //////// if (fmt_str[j - 1] === CLOSE) {
                        debug("current: " + fmt_str[j]);
                        debug("pencurrent: " + fmt_str[j - 1]);
                        escaped = true;
                        continue;
                    }
                    var key = fmt_str.substring(i + 2, j);
                    if (escaped === true) {
                        key = key.replace("\\}", "}");
                    }
                    debug("key: " + key);

                    // record whatever was between the previous match, if any,
                    // and "%{"
                    var prev = fmt_str.substring(first, i);
                    debug("adding " + prev);
                    out.push(prev);

                    // interpolate
                    debug("adding " + args[key]);
                    out.push(args[key]);

                    i = j + 1;
                    first = i ;

                    break;
                }
            }

            // if it isn't found, the format string was bogus:
            if (j === l) {
                throw "unterminated format string";
            }

            break;
        default:
            break;
        }
    }
    var last = fmt_str.substring(first, i);
    debug("adding " + last);
    out.push(last);

    return out.join("");
};

try {
    exports.fmt = fmt;
} catch (e) {
    switch (e.name) {
    case "ReferenceError":
        ; // nothing to do here
        break;
    default:
        throw e;
    }
}
<commit_msg>fix two of the three non-ridiculous errors pointed out by gjslint.<commit_after>var fmt = function (fmt_str, args) {
    // var debug = require("sys").debug;
    var debug = function (n) { };

    var SIGIL = '%',
        OPEN = '{',
        CLOSE = '}';
    var BACKSLASH = "\\";

    var out = [];
    var first = 0;

    var l = fmt_str.length;

    for (var i = 0; i < l; ++i) {
        var c0 = fmt_str[i],
            c1 = fmt_str[i + 1];
        switch (c0) {
        case SIGIL:
            // "%%" resolves to %
            if (c1 === '%') {
                out.push(fmt_str.substr(first, i + 1));
                ++i;
                first = i;
                continue;
            }

            // % followed by anything other than '{' is not allowed
            if (c1 !== OPEN) {
                throw "% must be followed by either '%' or '{'";
            }

            var escaped = false;
            // scan for the closing '}'
            for (var j = i + 2; j < l; ++j) {
                if (fmt_str[j] === CLOSE) {
                    // debug("current char: " + fmt_str[j]);
                    // debug("penultimate char: " + fmt_str[j - 1]);
                    if (fmt_str[j - 1] === BACKSLASH) {
                        //////// if (fmt_str[j - 1] === CLOSE) {
                        debug("current: " + fmt_str[j]);
                        debug("pencurrent: " + fmt_str[j - 1]);
                        escaped = true;
                        continue;
                    }
                    var key = fmt_str.substring(i + 2, j);
                    if (escaped === true) {
                        key = key.replace("\\}", "}");
                    }
                    debug("key: " + key);

                    // record whatever was between the previous match, if any,
                    // and "%{"
                    var prev = fmt_str.substring(first, i);
                    debug("adding " + prev);
                    out.push(prev);

                    // interpolate
                    debug("adding " + args[key]);
                    out.push(args[key]);

                    i = j + 1;
                    first = i;

                    break;
                }
            }

            // if it isn't found, the format string was bogus:
            if (j === l) {
                throw "unterminated format string";
            }

            break;
        default:
            break;
        }
    }
    var last = fmt_str.substring(first, i);
    debug("adding " + last);
    out.push(last);

    return out.join("");
};

try {
    exports.fmt = fmt;
} catch (e) {
    switch (e.name) {
    case "ReferenceError":
        // nothing to do here
        break;
    default:
        throw e;
    }
}
<|endoftext|>


<commit_before>import {
  isPlainObject
} from 'uni-shared'

const method = {
  OPTIONS: 'OPTIONS',
  GET: 'GET',
  HEAD: 'HEAD',
  POST: 'POST',
  PUT: 'PUT',
  DELETE: 'DELETE',
  TRACE: 'TRACE',
  CONNECT: 'CONNECT'
}
const dataType = {
  JSON: 'json'
}
const responseType = {
  TEXT: 'text',
  ARRAYBUFFER: 'arraybuffer'
}

const encode = encodeURIComponent

function stringifyQuery (url, data) {
  let str = url.split('#')
  const hash = str[1] || ''
  str = str[0].split('?')
  let query = str[1] || ''
  url = str[0]
  const search = query.split('&').filter(item => item)
  query = {}
  search.forEach(item => {
    item = item.split('=')
    query[item[0]] = item[1]
  })
  for (let key in data) {
    if (data.hasOwnProperty(key)) {
      if (isPlainObject(data[key])) {
        query[encode(key)] = encode(JSON.stringify(data[key]))
      } else {
        query[encode(key)] = encode(data[key])
      }
    }
  }
  query = Object.keys(query).map(item => `${item}=${query[item]}`).join('&')
  return url + (query ? '?' + query : '') + (hash ? '#' + hash : '')
}

export const request = {
  method: {
    type: String,
    validator (value, params) {
      value = (value || '').toUpperCase()
      params.method = Object.values(method).indexOf(value) < 0 ? method.GET : value
    }
  },
  data: {
    type: [Object, String, ArrayBuffer],
    validator (value, params) {
      params.data = value || ''
    }
  },
  url: {
    type: String,
    required: true,
    validator (value, params) {
      if (
        params.method === method.GET &&
        isPlainObject(params.data) &&
        Object.keys(params.data).length
      ) { // 将 method,data 校验提前,保证 url 校验时,method,data 已被格式化
        params.url = stringifyQuery(value, params.data)
      }
    }
  },
  header: {
    type: Object,
    validator (value, params) {
      const header = params.header = value || {}
      if (params.method !== method.GET) {
        if (Object.keys(header).filter(key => key.toLowerCase() === 'content-type')) {
          header['Content-Type'] = 'application/json'
        }
      }
    }
  },
  dataType: {
    type: String,
    validator (value, params) {
      params.dataType = (value || dataType.JSON).toLowerCase()
    }
  },
  responseType: {
    type: String,
    validator (value, params) {
      value = (value || '').toLowerCase()
      params.responseType = Object.values(responseType).indexOf(value) < 0 ? responseType.TEXT : value
    }
  }
}
<commit_msg>fix: 解决 H5 端 GET 请求之前出现 OPTIONS 预检请求的问题<commit_after>import {
  isPlainObject
} from 'uni-shared'

const method = {
  OPTIONS: 'OPTIONS',
  GET: 'GET',
  HEAD: 'HEAD',
  POST: 'POST',
  PUT: 'PUT',
  DELETE: 'DELETE',
  TRACE: 'TRACE',
  CONNECT: 'CONNECT'
}
const dataType = {
  JSON: 'json'
}
const responseType = {
  TEXT: 'text',
  ARRAYBUFFER: 'arraybuffer'
}

const encode = encodeURIComponent

function stringifyQuery (url, data) {
  let str = url.split('#')
  const hash = str[1] || ''
  str = str[0].split('?')
  let query = str[1] || ''
  url = str[0]
  const search = query.split('&').filter(item => item)
  query = {}
  search.forEach(item => {
    item = item.split('=')
    query[item[0]] = item[1]
  })
  for (let key in data) {
    if (data.hasOwnProperty(key)) {
      if (isPlainObject(data[key])) {
        query[encode(key)] = encode(JSON.stringify(data[key]))
      } else {
        query[encode(key)] = encode(data[key])
      }
    }
  }
  query = Object.keys(query).map(item => `${item}=${query[item]}`).join('&')
  return url + (query ? '?' + query : '') + (hash ? '#' + hash : '')
}

export const request = {
  method: {
    type: String,
    validator (value, params) {
      value = (value || '').toUpperCase()
      params.method = Object.values(method).indexOf(value) < 0 ? method.GET : value
    }
  },
  data: {
    type: [Object, String, ArrayBuffer],
    validator (value, params) {
      params.data = value || ''
    }
  },
  url: {
    type: String,
    required: true,
    validator (value, params) {
      if (
        params.method === method.GET &&
        isPlainObject(params.data) &&
        Object.keys(params.data).length
      ) { // 将 method,data 校验提前,保证 url 校验时,method,data 已被格式化
        params.url = stringifyQuery(value, params.data)
      }
    }
  },
  header: {
    type: Object,
    validator (value, params) {
      const header = params.header = value || {}
      if (params.method !== method.GET) {
        if (!Object.keys(header).find(key => key.toLowerCase() === 'content-type')) {
          header['Content-Type'] = 'application/json'
        }
      }
    }
  },
  dataType: {
    type: String,
    validator (value, params) {
      params.dataType = (value || dataType.JSON).toLowerCase()
    }
  },
  responseType: {
    type: String,
    validator (value, params) {
      value = (value || '').toLowerCase()
      params.responseType = Object.values(responseType).indexOf(value) < 0 ? responseType.TEXT : value
    }
  }
}
<|endoftext|>


<commit_before>import Parser from '../../src/Parser';
import visitorKeys from '../../src/visitorKeys';

import {expect} from 'chai';

describe('Parser::parse', () => {
    let code;

    beforeEach(() => {
        code = `class test {
			render = () => {}
		}`;

        delete visitorKeys.ClassProperty;
    });

    it('should iterate by types only', () => {
        try {
            new Parser().parse(code);
            expect(false).to.equal(true);

        } catch (e) {
            expect(e.message).to.equal('Cannot iterate using ClassProperty');
            expect(e.loc).to.deep.equal({line: 2, column: 3});
        }
    });
});
<commit_msg>Restore `ClassProperty` property of visitorKeys in tests<commit_after>import Parser from '../../src/Parser';
import visitorKeys from '../../src/visitorKeys';

import {expect} from 'chai';

describe('Parser::parse', () => {
    let code;
    let oldVisitorKey;

    beforeEach(() => {
        code = `class test {
			render = () => {}
		}`;

        oldVisitorKey = visitorKeys.ClassProperty;
        delete visitorKeys.ClassProperty;
    });

    afterEach(() => {
        visitorKeys.ClassProperty = oldVisitorKey;
    });

    it('should iterate by types only', () => {
        try {
            new Parser().parse(code);
            expect(false).to.equal(true);

        } catch (e) {
            expect(e.message).to.equal('Cannot iterate using ClassProperty');
            expect(e.loc).to.deep.equal({line: 2, column: 3});
        }
    });
});
<|endoftext|>


<commit_before>const mockSponsoredContent = {
  fairs: {
    "the-armory-show-2017": {
      activationText: "Lorem ipsum dolor sit amet",
      pressReleaseUrl: "https://www.example.com",
    },
  },
}

jest.mock("lib/sponsoredContent/data.json", () => mockSponsoredContent)

/* eslint-disable promise/always-return */
import { runQuery } from "schema/v2/test/utils"
import gql from "lib/gql"
import moment from "moment"

describe("Fair type", () => {
  const fair = {
    id: "the-armory-show-2017",
    name: "The Armory Show 2017",
    organizer: {
      profile_id: "the-armory-show",
    },
    mobile_image: {
      image_url: "circle-image.jpg",
    },
  }

  let query = gql`
    {
      fair(id: "the-armory-show-2017") {
        id
        name
        organizer {
          profile_id
          profile {
            is_publically_visible
          }
        }
        mobile_image {
          image_url
        }
      }
    }
  `

  let context = {
    fairLoader: sinon.stub().returns(Promise.resolve(fair)),
  }

  it("is_publically_visible returns true when profile is published", () => {
    const profile = {
      id: "the-armory-show",
      published: true,
      private: false,
    }

    context.profileLoader = sinon.stub().returns(Promise.resolve(profile))

    return runQuery(query, context).then(data => {
      expect(data).toEqual({
        fair: {
          id: "the-armory-show-2017",
          name: "The Armory Show 2017",
          organizer: {
            profile_id: "the-armory-show",
            profile: {
              is_publically_visible: true,
            },
          },
          mobile_image: {
            image_url: "circle-image.jpg",
          },
        },
      })
    })
  })

  it("is_publically_visible returns false when profile is not published", () => {
    const profile = {
      id: "context",
      published: false,
      private: false,
    }

    context.profileLoader = sinon.stub().returns(Promise.resolve(profile))

    return runQuery(query, context).then(data => {
      expect(data).toEqual({
        fair: {
          id: "the-armory-show-2017",
          name: "The Armory Show 2017",
          organizer: {
            profile_id: "the-armory-show",
            profile: {
              is_publically_visible: false,
            },
          },
          mobile_image: {
            image_url: "circle-image.jpg",
          },
        },
      })
    })
  })

  it("is_publically_visible returns false when profile is not published", () => {
    const profile = {
      id: "context",
      published: false,
      private: false,
    }

    context.profileLoader = sinon.stub().returns(Promise.resolve(profile))

    return runQuery(query, context).then(data => {
      expect(data).toEqual({
        fair: {
          id: "the-armory-show-2017",
          name: "The Armory Show 2017",
          organizer: {
            profile_id: "the-armory-show",
            profile: {
              is_publically_visible: false,
            },
          },
          mobile_image: {
            image_url: "circle-image.jpg",
          },
        },
      })
    })
  })

  it("includes sponsored content", async () => {
    query = gql`
      {
        fair(id: "the-armory-show-2017") {
          sponsoredContent {
            activationText
            pressReleaseUrl
          }
        }
      }
    `

    const result = await runQuery(query, context)

    expect(result.fair.sponsoredContent).toEqual({
      activationText: "Lorem ipsum dolor sit amet",
      pressReleaseUrl: "https://www.example.com",
    })
  })
})

describe("Fair", () => {
  let context = null
  beforeEach(() => {
    const data = {
      fair: {
        _id: 123,
        id: "aqua-art-miami-2018",
        artists_count: 1,
        artworks_count: 2,
        partners_count: 3,
        start_at: "2019-02-15T23:00:00+00:00",
        end_at: "2019-02-17T11:00:00+00:00",
        partner_shows_count: 4,
        name: "Aqua Art Miami 2018",
        exhibition_period: "Feb 15 – 17",
        exhibitors_grouped_by_name: [
          {
            letter: "A",
            exhibitors: [
              {
                name: "ArtHelix Gallery",
                id: "arthelix-gallery",
                profile_id: "arthelix-gallery",
                partner_id: "1234567890",
              },
            ],
          },
        ],
      },
    }
    context = {
      fairLoader: sinon.stub().returns(Promise.resolve(data.fair)),
      fairArtistsLoader: jest.fn().mockReturnValue(
        Promise.resolve({
          body: [
            {
              id: "1",
              name: "Foo Artist",
            },
          ],
          headers: {
            "x-total-count": 1,
          },
        })
      ),
      fairBoothsLoader: jest.fn().mockReturnValue(
        Promise.resolve({
          body: {
            results: [
              {
                id: "abxy-blk-and-blue",
              },
            ],
            next: "1234567890",
          },
        })
      ),
      artistsLoader: jest.fn().mockReturnValue(
        Promise.resolve([
          {
            name: "Foo Artist",
            id: "1",
          },
        ])
      ),
      fairPartnersLoader: sinon.stub().returns(
        Promise.resolve({
          body: {
            name: "ArtHelix Gallery",
            id: "arthelix-gallery",
            partner_id: "1234567890",
            partner_show_ids: ["arthelix-gallery"],
          },
          headers: {
            "x-total-count": 1,
          },
        })
      ),
    }
  })

  it("includes returns fair exhibitors grouped alphanumerically", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          id
          name
          exhibitors_grouped_by_name {
            letter
            exhibitors {
              name
              id
              partner_id
              profile_id
            }
          }
        }
      }
    `

    const data = await runQuery(query, context)

    expect(data).toEqual({
      fair: {
        id: "aqua-art-miami-2018",
        name: "Aqua Art Miami 2018",
        exhibitors_grouped_by_name: [
          {
            letter: "A",
            exhibitors: [
              {
                name: "ArtHelix Gallery",
                id: "arthelix-gallery",
                partner_id: "1234567890",
                profile_id: "arthelix-gallery",
              },
            ],
          },
        ],
      },
    })
  })

  it("Shows connection uses gravity cursor", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          shows: shows_connection(first: 0, after: "") {
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
      }
    `

    const data = await runQuery(query, context)
    expect(data).toEqual({
      fair: {
        shows: {
          pageInfo: {
            endCursor: "1234567890",
            hasNextPage: true,
          },
        },
      },
    })
  })

  it("includes a formatted exhibition period", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          exhibition_period
        }
      }
    `

    const data = await runQuery(query, context)
    expect(data).toEqual({
      fair: {
        exhibition_period: "Feb 15 – 17",
      },
    })
  })

  it("includes artists associated with the fair", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          artists(first: 1) {
            edges {
              node {
                id
                name
              }
            }
          }
        }
      }
    `

    const data = await runQuery(query, context)

    expect(data).toEqual({
      fair: {
        artists: {
          edges: [
            {
              node: {
                id: "1",
                name: "Foo Artist",
              },
            },
          ],
        },
      },
    })
  })

  describe("isActive flag", () => {
    describe("when active_start_at and end_at are in the past", () => {
      it("is false ", async () => {
        const mockFair = {
          id: "this-fair-was-active",
          active_start_at: moment()
            .subtract(14, "days")
            .toISOString(),
          end_at: moment()
            .subtract(7, "days")
            .toISOString(),
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(mockFair))
        context = {
          fairLoader: mockFairLoader,
        }

        const query = gql`
          {
            fair(id: "this-fair-was-active") {
              isActive
            }
          }
        `

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            isActive: true,
          },
        })
      })
    })

    describe("when active_start_at is in the past and end_at is in the future", () => {
      it("is true ", async () => {
        const mockFair = {
          id: "this-fair-is-active",
          active_start_at: moment()
            .subtract(7, "days")
            .toISOString(),
          end_at: moment()
            .add(7, "days")
            .toISOString(),
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(mockFair))
        context = {
          fairLoader: mockFairLoader,
        }

        const query = gql`
          {
            fair(id: "this-fair-is-active") {
              isActive
            }
          }
        `

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            isActive: true,
          },
        })
      })
    })

    describe("when active_start_at and end_at are in the future", () => {
      it("is false ", async () => {
        const mockFair = {
          id: "this-fair-not-yet-active",
          active_start_at: moment()
            .add(7, "days")
            .toISOString(),
          end_at: moment()
            .add(14, "days")
            .toISOString(),
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(mockFair))
        context = {
          fairLoader: mockFairLoader,
        }

        const query = gql`
          {
            fair(id: "this-fair-not-yet-active") {
              isActive
            }
          }
        `

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            isActive: false,
          },
        })
      })
    })
  })

  describe("formattedOpeningHours", () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          formattedOpeningHours
        }
      }
    `

    const realNow = Date.now
    beforeEach(() => {
      Date.now = () => new Date("2019-01-30T03:24:00")
    })
    afterEach(() => {
      Date.now = realNow
    })

    describe("with future dates", () => {
      it("The fair is opening on the hour", async () => {
        const fairData = {
          start_at: "2019-02-06T12:00:56+00:00",
          end_at: "2019-02-30T12:34:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Opens Feb 6 at 12:00pm UTC",
          },
        })
      })

      it("The fair is opening with minutes", async () => {
        const fairData = {
          start_at: "2019-02-06T12:30:56+00:00",
          end_at: "2019-02-30T12:34:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Opens Feb 6 at 12:30pm UTC",
          },
        })
      })
    })

    describe("with running dates", () => {
      it("The fair is closing", async () => {
        const fairData = {
          start_at: "2019-01-20T12:34:56+00:00",
          end_at: "2019-02-04T12:00:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Closes Feb 4 at 12:00pm UTC",
          },
        })
      })
    })

    describe("with past dates", () => {
      it("The fair is closed", async () => {
        const fairData = {
          start_at: "2019-01-01T12:34:56+00:00",
          end_at: "2019-01-10T12:34:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Closed",
          },
        })
      })
    })
  })

  describe("fair counts", () => {
    let counts

    beforeEach(async () => {
      const query = gql`
        {
          fair(id: "aqua-art-miami-2018") {
            counts {
              artists
              artworks
              partner_shows
              partners
            }
          }
        }
      `

      const data = await runQuery(query, context)
      counts = data.fair.counts
    })

    it("includes the total number of artists", () => {
      expect(counts).toMatchObject({
        artists: 1,
      })
    })

    it("includes the total number of artworks", () => {
      expect(counts).toMatchObject({
        artworks: 2,
      })
    })

    it("includes the total number of partners", () => {
      expect(counts).toMatchObject({
        partners: 3,
      })
    })

    it("includes the total number of partner_shows", () => {
      expect(counts).toMatchObject({
        partner_shows: 4,
      })
    })
  })
})
<commit_msg>Fix v2 fair test<commit_after>const mockSponsoredContent = {
  fairs: {
    "the-armory-show-2017": {
      activationText: "Lorem ipsum dolor sit amet",
      pressReleaseUrl: "https://www.example.com",
    },
  },
}

jest.mock("lib/sponsoredContent/data.json", () => mockSponsoredContent)

/* eslint-disable promise/always-return */
import { runQuery } from "schema/v2/test/utils"
import gql from "lib/gql"
import moment from "moment"

describe("Fair type", () => {
  const fair = {
    id: "the-armory-show-2017",
    name: "The Armory Show 2017",
    organizer: {
      profile_id: "the-armory-show",
    },
    mobile_image: {
      image_url: "circle-image.jpg",
    },
  }

  let query = gql`
    {
      fair(id: "the-armory-show-2017") {
        slug
        name
        organizer {
          profileID
          profile {
            isPublicallyVisible
          }
        }
        mobileImage {
          imageURL
        }
      }
    }
  `

  let context = {
    fairLoader: sinon.stub().returns(Promise.resolve(fair)),
  }

  it("is_publically_visible returns true when profile is published", () => {
    const profile = {
      id: "the-armory-show",
      published: true,
      private: false,
    }

    context.profileLoader = sinon.stub().returns(Promise.resolve(profile))

    return runQuery(query, context).then(data => {
      expect(data).toEqual({
        fair: {
          slug: "the-armory-show-2017",
          name: "The Armory Show 2017",
          organizer: {
            profileID: "the-armory-show",
            profile: {
              isPublicallyVisible: true,
            },
          },
          mobileImage: {
            imageURL: "circle-image.jpg",
          },
        },
      })
    })
  })

  it("is_publically_visible returns false when profile is not published", () => {
    const profile = {
      id: "context",
      published: false,
      private: false,
    }

    context.profileLoader = sinon.stub().returns(Promise.resolve(profile))

    return runQuery(query, context).then(data => {
      expect(data).toEqual({
        fair: {
          slug: "the-armory-show-2017",
          name: "The Armory Show 2017",
          organizer: {
            profileID: "the-armory-show",
            profile: {
              isPublicallyVisible: false,
            },
          },
          mobileImage: {
            imageURL: "circle-image.jpg",
          },
        },
      })
    })
  })

  it("is_publically_visible returns false when profile is not published", () => {
    const profile = {
      id: "context",
      published: false,
      private: false,
    }

    context.profileLoader = sinon.stub().returns(Promise.resolve(profile))

    return runQuery(query, context).then(data => {
      expect(data).toEqual({
        fair: {
          slug: "the-armory-show-2017",
          name: "The Armory Show 2017",
          organizer: {
            profileID: "the-armory-show",
            profile: {
              isPublicallyVisible: false,
            },
          },
          mobileImage: {
            imageURL: "circle-image.jpg",
          },
        },
      })
    })
  })

  it("includes sponsored content", async () => {
    query = gql`
      {
        fair(id: "the-armory-show-2017") {
          sponsoredContent {
            activationText
            pressReleaseUrl
          }
        }
      }
    `

    const result = await runQuery(query, context)

    expect(result.fair.sponsoredContent).toEqual({
      activationText: "Lorem ipsum dolor sit amet",
      pressReleaseUrl: "https://www.example.com",
    })
  })
})

describe("Fair", () => {
  let context = null
  beforeEach(() => {
    const data = {
      fair: {
        _id: 123,
        id: "aqua-art-miami-2018",
        artists_count: 1,
        artworks_count: 2,
        partners_count: 3,
        start_at: "2019-02-15T23:00:00+00:00",
        end_at: "2019-02-17T11:00:00+00:00",
        partner_shows_count: 4,
        name: "Aqua Art Miami 2018",
        exhibition_period: "Feb 15 – 17",
        exhibitors_grouped_by_name: [
          {
            letter: "A",
            exhibitors: [
              {
                name: "ArtHelix Gallery",
                id: "arthelix-gallery",
                profile_id: "arthelix-gallery",
                partner_id: "1234567890",
              },
            ],
          },
        ],
      },
    }
    context = {
      fairLoader: sinon.stub().returns(Promise.resolve(data.fair)),
      fairArtistsLoader: jest.fn().mockReturnValue(
        Promise.resolve({
          body: [
            {
              id: "foo-artist",
              name: "Foo Artist",
            },
          ],
          headers: {
            "x-total-count": 1,
          },
        })
      ),
      fairBoothsLoader: jest.fn().mockReturnValue(
        Promise.resolve({
          body: {
            results: [
              {
                id: "abxy-blk-and-blue",
              },
            ],
            next: "1234567890",
          },
        })
      ),
      artistsLoader: jest.fn().mockReturnValue(
        Promise.resolve([
          {
            name: "Foo Artist",
            id: "foo-artist",
          },
        ])
      ),
      fairPartnersLoader: sinon.stub().returns(
        Promise.resolve({
          body: {
            name: "ArtHelix Gallery",
            id: "arthelix-gallery",
            partner_id: "1234567890",
            partner_show_ids: ["arthelix-gallery"],
          },
          headers: {
            "x-total-count": 1,
          },
        })
      ),
    }
  })

  it("includes returns fair exhibitors grouped alphanumerically", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          slug
          name
          exhibitorsGroupedByName {
            letter
            exhibitors {
              name
              slug
              partnerID
              profileID
            }
          }
        }
      }
    `

    const data = await runQuery(query, context)

    expect(data).toEqual({
      fair: {
        slug: "aqua-art-miami-2018",
        name: "Aqua Art Miami 2018",
        exhibitorsGroupedByName: [
          {
            letter: "A",
            exhibitors: [
              {
                name: "ArtHelix Gallery",
                slug: "arthelix-gallery",
                partnerID: "1234567890",
                profileID: "arthelix-gallery",
              },
            ],
          },
        ],
      },
    })
  })

  it("Shows connection uses gravity cursor", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          shows: showsConnection(first: 0, after: "") {
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
      }
    `

    const data = await runQuery(query, context)
    expect(data).toEqual({
      fair: {
        shows: {
          pageInfo: {
            endCursor: "1234567890",
            hasNextPage: true,
          },
        },
      },
    })
  })

  it("includes a formatted exhibition period", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          exhibitionPeriod
        }
      }
    `

    const data = await runQuery(query, context)
    expect(data).toEqual({
      fair: {
        exhibitionPeriod: "Feb 15 – 17",
      },
    })
  })

  it("includes artists associated with the fair", async () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          artistsConnection(first: 1) {
            edges {
              node {
                slug
                name
              }
            }
          }
        }
      }
    `

    const data = await runQuery(query, context)

    expect(data).toEqual({
      fair: {
        artistsConnection: {
          edges: [
            {
              node: {
                slug: "foo-artist",
                name: "Foo Artist",
              },
            },
          ],
        },
      },
    })
  })

  describe("isActive flag", () => {
    describe("when active_start_at and end_at are in the past", () => {
      it("is false ", async () => {
        const mockFair = {
          id: "this-fair-was-active",
          active_start_at: moment()
            .subtract(14, "days")
            .toISOString(),
          end_at: moment()
            .subtract(7, "days")
            .toISOString(),
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(mockFair))
        context = {
          fairLoader: mockFairLoader,
        }

        const query = gql`
          {
            fair(id: "this-fair-was-active") {
              isActive
            }
          }
        `

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            isActive: true,
          },
        })
      })
    })

    describe("when active_start_at is in the past and end_at is in the future", () => {
      it("is true ", async () => {
        const mockFair = {
          id: "this-fair-is-active",
          active_start_at: moment()
            .subtract(7, "days")
            .toISOString(),
          end_at: moment()
            .add(7, "days")
            .toISOString(),
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(mockFair))
        context = {
          fairLoader: mockFairLoader,
        }

        const query = gql`
          {
            fair(id: "this-fair-is-active") {
              isActive
            }
          }
        `

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            isActive: true,
          },
        })
      })
    })

    describe("when active_start_at and end_at are in the future", () => {
      it("is false ", async () => {
        const mockFair = {
          id: "this-fair-not-yet-active",
          active_start_at: moment()
            .add(7, "days")
            .toISOString(),
          end_at: moment()
            .add(14, "days")
            .toISOString(),
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(mockFair))
        context = {
          fairLoader: mockFairLoader,
        }

        const query = gql`
          {
            fair(id: "this-fair-not-yet-active") {
              isActive
            }
          }
        `

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            isActive: false,
          },
        })
      })
    })
  })

  describe("formattedOpeningHours", () => {
    const query = gql`
      {
        fair(id: "aqua-art-miami-2018") {
          formattedOpeningHours
        }
      }
    `

    const realNow = Date.now
    beforeEach(() => {
      Date.now = () => new Date("2019-01-30T03:24:00")
    })
    afterEach(() => {
      Date.now = realNow
    })

    describe("with future dates", () => {
      it("The fair is opening on the hour", async () => {
        const fairData = {
          start_at: "2019-02-06T12:00:56+00:00",
          end_at: "2019-02-30T12:34:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Opens Feb 6 at 12:00pm UTC",
          },
        })
      })

      it("The fair is opening with minutes", async () => {
        const fairData = {
          start_at: "2019-02-06T12:30:56+00:00",
          end_at: "2019-02-30T12:34:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Opens Feb 6 at 12:30pm UTC",
          },
        })
      })
    })

    describe("with running dates", () => {
      it("The fair is closing", async () => {
        const fairData = {
          start_at: "2019-01-20T12:34:56+00:00",
          end_at: "2019-02-04T12:00:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Closes Feb 4 at 12:00pm UTC",
          },
        })
      })
    })

    describe("with past dates", () => {
      it("The fair is closed", async () => {
        const fairData = {
          start_at: "2019-01-01T12:34:56+00:00",
          end_at: "2019-01-10T12:34:56+00:00",
        }

        const mockFairLoader = jest.fn(() => Promise.resolve(fairData))
        context = {
          fairLoader: mockFairLoader,
        }

        const data = await runQuery(query, context)

        expect(data).toEqual({
          fair: {
            formattedOpeningHours: "Closed",
          },
        })
      })
    })
  })

  describe("fair counts", () => {
    let counts

    beforeEach(async () => {
      const query = gql`
        {
          fair(id: "aqua-art-miami-2018") {
            counts {
              artists
              artworks
              partnerShows
              partners
            }
          }
        }
      `

      const data = await runQuery(query, context)
      counts = data.fair.counts
    })

    it("includes the total number of artists", () => {
      expect(counts).toMatchObject({
        artists: 1,
      })
    })

    it("includes the total number of artworks", () => {
      expect(counts).toMatchObject({
        artworks: 2,
      })
    })

    it("includes the total number of partners", () => {
      expect(counts).toMatchObject({
        partners: 3,
      })
    })

    it("includes the total number of partner_shows", () => {
      expect(counts).toMatchObject({
        partnerShows: 4,
      })
    })
  })
})
<|endoftext|>