<commit_before>/*global setImmediate:true*/

if (typeof setImmediate === 'undefined') {
    setImmediate = process.nextTick;
}

var BufferedStream = require('bufferedstream'),
    URL = require('url'),
    http = require('http'),
    _ = require('underscore'),
    messy = require('messy'),
    formatHeaderName = messy.formatHeaderName;

function formatHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[formatHeaderName(headerName)] = obj[headerName];
    });
    return resultObj;
}

function lowerCaseHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[headerName.toLowerCase()] = obj[headerName];
    });
    return resultObj;
}

function isTextualContentType(contentType) {
    if (typeof contentType === 'string') {
        contentType = contentType.toLowerCase().trim().replace(/\s*;.*$/, '');
        return (
            /^text\//.test(contentType) ||
            /^application\/(json|javascript)$/.test(contentType) ||
            /^application\/xml/.test(contentType) ||
            /\+xml$/.test(contentType)
        );
    }
    return false;
}

function bufferCanBeInterpretedAsUtf8(buffer) {
    // Hack: Since Buffer.prototype.toString('utf-8') is very forgiving, convert the buffer to a string
    // with percent-encoded octets, then see if decodeURIComponent accepts it.
    try {
        decodeURIComponent(Array.prototype.map.call(buffer, function (octet) {
            return '%' + (octet < 16 ? '0' : '') + octet.toString(16);
        }).join(''));
    } catch (e) {
        return false;
    }
    return true;
}

module.exports = function (expect) {
    expect.addType({
        equal: function (httpRequest1, httpRequest2) {
            return httpRequest1.equal(httpRequest2);
        },
        identify: function (obj) {
            return obj instanceof messy.Headers;
        },
        inspect: function (output, headers, inspect) {
            headers.getNames().forEach(function (headerName) {
                headers.valuesByName[headerName].forEach(function (headerValue) {
                    output
                        .text(messy.formatHeaderName(headerName + ':'), 'gray')
                        .text(headerValue, 'cyan');
                });
            });
            return output;
        }
    }).addType({
        identify: function (obj) {
            return obj instanceof messy.HttpRequest;
        },
        equal: function (httpRequest1, httpRequest2) {
            return httpRequest1.equal(httpRequest2);
        },
        inspect: function (output, httpRequest, inspect) {
            output
                .text(httpRequest.method, 'blue')
                .text(' ')
                .text(httpRequest.url, 'gray')
                .text(' ')
                .text(httpRequest.protocol, 'cyan');

            inspect(output, httpRequest.headers);
            return output;
        }
    }).addType({
        equal: function (httpResponse1, httpResponse2) {
            return httpResponse1.equal(httpResponse2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpResponse;
        },
        inspect: function (output, httpResponse, inspect) {
            output
                .text(httpResponse.protocol, 'blue')
                .text(' ')
                .text(httpResponse.statusCode, 'cyan')
                .text(' ')
                .text(httpResponse.statusText, 'yellow');
            inspect(output, httpResponse.headers);
            return output;
        }
    });

    expect.addAssertion('to be middleware that processes', function (expect, subject, value, _done) {
        this.errorMode = 'nested';
        expect(_done, 'to be a function');

        var doneCalled = false;

        function done() {
            if (!doneCalled) {
                doneCalled = true;
                return _done.apply(this, arguments);
            }
        }

        var requestProperties = typeof value.request === 'string' ? {url: value.request} : _.extend({}, value.request);
        if (typeof requestProperties.url === 'string') {
            var matchMethod = requestProperties.url.match(/^([A-Z]+) ([\s\S]*)$/);
            if (matchMethod) {
                requestProperties.method = requestProperties.method || matchMethod[1];
                requestProperties.url = matchMethod[2];
            }
        }

        requestProperties.method = requestProperties.method || 'GET';

        requestProperties.headers = _.extend({}, requestProperties.headers); // Make a shallow copy so it's OK to add to it

        var expectedResponseProperties;

        if (typeof value.response === 'number') {
            expectedResponseProperties = {statusCode: value.response};
        } else if (typeof value.response === 'string' || Buffer.isBuffer(value.response)) {
            expectedResponseProperties = {body: value.response};
        } else {
            expectedResponseProperties = _.extend({}, value.response);
        }
        requestProperties.headers = lowerCaseHeaderNames(requestProperties.headers);

        if (/^https?:\/\//.test(requestProperties.url)) {
            var urlObj = URL.parse(requestProperties.url);
            if (typeof requestProperties.headers.host === 'undefined') {
                requestProperties.headers.host = urlObj.host;
            }
            if (urlObj.protocol === 'https:' && typeof requestProperties.https === 'undefined') {
                requestProperties.https = true;
            }
            requestProperties.url = urlObj.path;
        }

        var responseProperties = {},
            requestStream,
            body = requestProperties.body,
            req;

        if (typeof body !== 'undefined') {
            delete requestProperties.body;
            if (body.pipe) {
                requestStream = body;
                if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'multipart/form-data; boundary=' + requestStream.getBoundary();
                    // form-data pauses its streams by default for some reason:
                    setImmediate(function () {
                        requestStream.resume();
                    });
                }
                requestProperties.headers['transfer-encoding'] = requestProperties.headers['transfer-encoding'] || 'chunked';
                requestStream.on('data', function (chunk) {
                    req.emit('data', new Buffer(chunk, 'utf-8'));
                }).on('end', function () {
                    req.emit('end');
                }).on('error', function (err) {
                    req.emit('error', err);
                });
            } else {
                if (typeof body === 'object' && !Buffer.isBuffer(body)) {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'application/json';
                    body = JSON.stringify(body);
                }

                if (!Buffer.isBuffer(body)) {
                    body = new Buffer(String(body), 'utf-8');
                }
                if (!('content-length' in requestProperties.headers) && !('transfer-encoding' in requestProperties.headers)) {
                    requestProperties.headers['content-length'] = String(body.length);
                }
                requestStream = new BufferedStream();
                setImmediate(function () {
                    req.emit('data', body);
                    req.emit('end');
                });
            }
        } else {
            requestStream = new BufferedStream();
            requestStream.destroy = function () {
                responseProperties.requestDestroyed = true;
            };
            setImmediate(function () {
                requestStream.emit('end');
                req.emit('end');
            });
        }

        req = new http.IncomingMessage(requestStream);
        req.connection = req.connection || {};
        req.connection.encrypted = !!requestProperties.https;
        delete requestProperties.https;
        req.connection.remoteAddress = requestProperties.remoteAddress || requestProperties.ip || '127.0.0.1';
        delete requestProperties.ip;
        delete requestProperties.remoteAddress;
        _.extend(req, requestProperties);

        var res = new http.ServerResponse(req);
        _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
        res.connection = new BufferedStream();
        res.connection._httpMessage = res;

        var nextCallCount = 0,
            next = function (err, _req, _res, _next) {
                nextCallCount += 1;
                if (typeof err === 'number') {
                    var statusCode = err;
                    err = new Error('' + statusCode);
                    err.statusCode = statusCode;
                }
                res.statusCode = err && (err.statusCode || err.status) || 404;
                doTheAssertions(err);
            };

        var responseBodyChunks = [],
            isDestroyed = false,
            isAsync = false;

        setImmediate(function () {
            isAsync = true;
        });

        var context = {req: req, res: res, next: next};

        function doTheAssertions(errorPassedToNext) {
            // Apparently setImmediate (or throwing an exception) is not an option here. If we don't call done, errors won't make it into mocha.
            var err = null;
            try {
                var expectedResponseHeaders,
                    responseProperties = context.responseProperties = {
                        url: req.url,
                        strictAsync: isAsync,
                        errorPassedToNext: false,
                        statusCode: res.statusCode,
                        isDestroyed: isDestroyed,
                        nextCalled: nextCallCount > 0
                    };

                if (errorPassedToNext) {
                    responseProperties.errorPassedToNext = errorPassedToNext;
                    if (typeof expectedResponseProperties.errorPassedToNext !== 'undefined') {
                        if (expectedResponseProperties.errorPassedToNext === true) {
                            responseProperties.errorPassedToNext = true;
                        } else if (typeof expectedResponseProperties.errorPassedToNext === 'string') {
                            responseProperties.errorPassedToNext = errorPassedToNext.message;
                        } else {
                            responseProperties.errorPassedToNext = errorPassedToNext;
                        }
                    } else {
                        return setImmediate(function () {
                            done(errorPassedToNext);
                        });
                    }
                }

                if (responseBodyChunks.length > 0) {
                    responseProperties.body = Buffer.concat(responseBodyChunks.map(function (bufferOrString) {
                        if (!Buffer.isBuffer(bufferOrString)) {
                            return new Buffer(String(bufferOrString), 'utf-8');
                        } else {
                            return bufferOrString;
                        }
                    }));
                    if ((typeof expectedResponseProperties.body === 'string' || (typeof expectedResponseProperties.body === 'object' && !Buffer.isBuffer(expectedResponseProperties.body))) || (typeof expectedResponseProperties.body === 'undefined' && bufferCanBeInterpretedAsUtf8(responseProperties.body) && isTextualContentType(res._headers['content-type']))) {
                        try {
                            responseProperties.body = responseProperties.body.toString('utf-8');
                        } catch (e) {
                            // The body cannot be intepreted as utf-8, keep it as a Buffer instance
                        }
                    }
                    if (/^application\/json\b/i.test(res._headers['content-type']) && typeof responseProperties.body === 'string' && (typeof expectedResponseProperties.body === 'undefined' || (typeof expectedResponseProperties.body === 'object' && !Buffer.isBuffer(expectedResponseProperties.body)))) {
                        responseProperties.body = JSON.parse(responseProperties.body);
                    } else if (Buffer.isBuffer(expectedResponseProperties.body) && (!responseProperties.body || typeof responseProperties.body === 'string')) {
                        responseProperties.body = new Buffer(responseProperties.body, 'utf-8');
                    }
                } else if (Buffer.isBuffer(expectedResponseProperties.body) && expectedResponseProperties.body.length === 0) {
                    responseProperties.body = new Buffer([]);
                } else if (expectedResponseProperties.body === '') {
                    responseProperties.body = '';
                }

                var httpResponse = new messy.HttpResponse({
                    headers: res._header || undefined,
                    body: responseProperties.body
                });

                var headersMatch = true;
                if (typeof expectedResponseProperties.headers !== 'undefined') {
                    var expectedResponseHeaders = new messy.Headers(expectedResponseProperties.headers),
                        headersThatMustNotBePresent = expectedResponseProperties.headers && typeof expectedResponseProperties.headers === 'object' && Object.keys(expectedResponseProperties.headers).filter(function (headerName) {
                            return typeof expectedResponseProperties.headers[headerName] === 'undefined';
                        });
                    delete expectedResponseProperties.headers;
                    if (expectedResponseHeaders.getNames().some(function (headerName) {
                        return !httpResponse.hasHeader(headerName, expectedResponseProperties.headers[headerName]);
                    })) {
                        headersMatch = false;
                    } else if (headersThatMustNotBePresent.some(function (headerName) {
                        return httpResponse.hasHeader(headerName);
                    })) {
                        headersMatch = false;
                    }
                }

                var bodyMatches = !('body' in expectedResponseProperties) || expect.equal(responseProperties.body, expectedResponseProperties.body);
                delete expectedResponseProperties.body;
                expect(responseProperties, 'to have properties', expectedResponseProperties);
                if (!headersMatch || !bodyMatches) {
                    expect.fail(expect.inspect(expect.output.clone(), httpResponse));
                }
            } catch (assertionError) {
                err = assertionError;
            }
            setImmediate(function () {
                done(err, context);
            });
        }
        ['write', 'end', 'destroy'].forEach(function (methodName) {
            var orig = res[methodName];
            res[methodName] = function (chunk, encoding) {
                var returnValue = orig.apply(this, arguments);
                if (methodName !== 'destroy') {
                    if (encoding && !/^utf-?8$/i.test(encoding)) {
                        throw new Error('encoding parameter not supported except for "utf-8": ' + encoding);
                    }
                    if (chunk) {
                        responseBodyChunks.push(chunk);
                    }
                }
                if (methodName === 'end' || methodName === 'destroy') {
                    isDestroyed = methodName === 'destroy';
                    doTheAssertions();
                }
                return returnValue;
            };
        });
        subject(req, res, next);
    });
};
<commit_msg>jshint<commit_after>/*global setImmediate:true*/

if (typeof setImmediate === 'undefined') {
    setImmediate = process.nextTick;
}

var BufferedStream = require('bufferedstream'),
    URL = require('url'),
    http = require('http'),
    _ = require('underscore'),
    messy = require('messy'),
    formatHeaderName = messy.formatHeaderName;

function lowerCaseHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[headerName.toLowerCase()] = obj[headerName];
    });
    return resultObj;
}

function isTextualContentType(contentType) {
    if (typeof contentType === 'string') {
        contentType = contentType.toLowerCase().trim().replace(/\s*;.*$/, '');
        return (
            /^text\//.test(contentType) ||
            /^application\/(json|javascript)$/.test(contentType) ||
            /^application\/xml/.test(contentType) ||
            /\+xml$/.test(contentType)
        );
    }
    return false;
}

function bufferCanBeInterpretedAsUtf8(buffer) {
    // Hack: Since Buffer.prototype.toString('utf-8') is very forgiving, convert the buffer to a string
    // with percent-encoded octets, then see if decodeURIComponent accepts it.
    try {
        decodeURIComponent(Array.prototype.map.call(buffer, function (octet) {
            return '%' + (octet < 16 ? '0' : '') + octet.toString(16);
        }).join(''));
    } catch (e) {
        return false;
    }
    return true;
}

module.exports = function (expect) {
    expect.addType({
        equal: function (httpRequest1, httpRequest2) {
            return httpRequest1.equal(httpRequest2);
        },
        identify: function (obj) {
            return obj instanceof messy.Headers;
        },
        inspect: function (output, headers, inspect) {
            headers.getNames().forEach(function (headerName) {
                headers.valuesByName[headerName].forEach(function (headerValue) {
                    output
                        .text(messy.formatHeaderName(headerName + ':'), 'gray')
                        .text(headerValue, 'cyan');
                });
            });
            return output;
        }
    }).addType({
        identify: function (obj) {
            return obj instanceof messy.HttpRequest;
        },
        equal: function (httpRequest1, httpRequest2) {
            return httpRequest1.equal(httpRequest2);
        },
        inspect: function (output, httpRequest, inspect) {
            output
                .text(httpRequest.method, 'blue')
                .text(' ')
                .text(httpRequest.url, 'gray')
                .text(' ')
                .text(httpRequest.protocol, 'cyan');

            inspect(output, httpRequest.headers);
            return output;
        }
    }).addType({
        equal: function (httpResponse1, httpResponse2) {
            return httpResponse1.equal(httpResponse2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpResponse;
        },
        inspect: function (output, httpResponse, inspect) {
            output
                .text(httpResponse.protocol, 'blue')
                .text(' ')
                .text(httpResponse.statusCode, 'cyan')
                .text(' ')
                .text(httpResponse.statusText, 'yellow');
            inspect(output, httpResponse.headers);
            return output;
        }
    });

    expect.addAssertion('to be middleware that processes', function (expect, subject, value, _done) {
        this.errorMode = 'nested';
        expect(_done, 'to be a function');

        var doneCalled = false;

        function done() {
            if (!doneCalled) {
                doneCalled = true;
                return _done.apply(this, arguments);
            }
        }

        var requestProperties = typeof value.request === 'string' ? {url: value.request} : _.extend({}, value.request);
        if (typeof requestProperties.url === 'string') {
            var matchMethod = requestProperties.url.match(/^([A-Z]+) ([\s\S]*)$/);
            if (matchMethod) {
                requestProperties.method = requestProperties.method || matchMethod[1];
                requestProperties.url = matchMethod[2];
            }
        }

        requestProperties.method = requestProperties.method || 'GET';

        requestProperties.headers = _.extend({}, requestProperties.headers); // Make a shallow copy so it's OK to add to it

        var expectedResponseProperties;

        if (typeof value.response === 'number') {
            expectedResponseProperties = {statusCode: value.response};
        } else if (typeof value.response === 'string' || Buffer.isBuffer(value.response)) {
            expectedResponseProperties = {body: value.response};
        } else {
            expectedResponseProperties = _.extend({}, value.response);
        }
        requestProperties.headers = lowerCaseHeaderNames(requestProperties.headers);

        if (/^https?:\/\//.test(requestProperties.url)) {
            var urlObj = URL.parse(requestProperties.url);
            if (typeof requestProperties.headers.host === 'undefined') {
                requestProperties.headers.host = urlObj.host;
            }
            if (urlObj.protocol === 'https:' && typeof requestProperties.https === 'undefined') {
                requestProperties.https = true;
            }
            requestProperties.url = urlObj.path;
        }

        var responseProperties = {},
            requestStream,
            body = requestProperties.body,
            req;

        if (typeof body !== 'undefined') {
            delete requestProperties.body;
            if (body.pipe) {
                requestStream = body;
                if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'multipart/form-data; boundary=' + requestStream.getBoundary();
                    // form-data pauses its streams by default for some reason:
                    setImmediate(function () {
                        requestStream.resume();
                    });
                }
                requestProperties.headers['transfer-encoding'] = requestProperties.headers['transfer-encoding'] || 'chunked';
                requestStream.on('data', function (chunk) {
                    req.emit('data', new Buffer(chunk, 'utf-8'));
                }).on('end', function () {
                    req.emit('end');
                }).on('error', function (err) {
                    req.emit('error', err);
                });
            } else {
                if (typeof body === 'object' && !Buffer.isBuffer(body)) {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'application/json';
                    body = JSON.stringify(body);
                }

                if (!Buffer.isBuffer(body)) {
                    body = new Buffer(String(body), 'utf-8');
                }
                if (!('content-length' in requestProperties.headers) && !('transfer-encoding' in requestProperties.headers)) {
                    requestProperties.headers['content-length'] = String(body.length);
                }
                requestStream = new BufferedStream();
                setImmediate(function () {
                    req.emit('data', body);
                    req.emit('end');
                });
            }
        } else {
            requestStream = new BufferedStream();
            requestStream.destroy = function () {
                responseProperties.requestDestroyed = true;
            };
            setImmediate(function () {
                requestStream.emit('end');
                req.emit('end');
            });
        }

        req = new http.IncomingMessage(requestStream);
        req.connection = req.connection || {};
        req.connection.encrypted = !!requestProperties.https;
        delete requestProperties.https;
        req.connection.remoteAddress = requestProperties.remoteAddress || requestProperties.ip || '127.0.0.1';
        delete requestProperties.ip;
        delete requestProperties.remoteAddress;
        _.extend(req, requestProperties);

        var res = new http.ServerResponse(req);
        _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
        res.connection = new BufferedStream();
        res.connection._httpMessage = res;

        var nextCallCount = 0,
            next = function (err, _req, _res, _next) {
                nextCallCount += 1;
                if (typeof err === 'number') {
                    var statusCode = err;
                    err = new Error('' + statusCode);
                    err.statusCode = statusCode;
                }
                res.statusCode = err && (err.statusCode || err.status) || 404;
                doTheAssertions(err);
            };

        var responseBodyChunks = [],
            isDestroyed = false,
            isAsync = false;

        setImmediate(function () {
            isAsync = true;
        });

        var context = {req: req, res: res, next: next};

        function doTheAssertions(errorPassedToNext) {
            // Apparently setImmediate (or throwing an exception) is not an option here. If we don't call done, errors won't make it into mocha.
            var err = null;
            try {
                var responseProperties = context.responseProperties = {
                    url: req.url,
                    strictAsync: isAsync,
                    errorPassedToNext: false,
                    statusCode: res.statusCode,
                    isDestroyed: isDestroyed,
                    nextCalled: nextCallCount > 0
                };

                if (errorPassedToNext) {
                    responseProperties.errorPassedToNext = errorPassedToNext;
                    if (typeof expectedResponseProperties.errorPassedToNext !== 'undefined') {
                        if (expectedResponseProperties.errorPassedToNext === true) {
                            responseProperties.errorPassedToNext = true;
                        } else if (typeof expectedResponseProperties.errorPassedToNext === 'string') {
                            responseProperties.errorPassedToNext = errorPassedToNext.message;
                        } else {
                            responseProperties.errorPassedToNext = errorPassedToNext;
                        }
                    } else {
                        return setImmediate(function () {
                            done(errorPassedToNext);
                        });
                    }
                }

                if (responseBodyChunks.length > 0) {
                    responseProperties.body = Buffer.concat(responseBodyChunks.map(function (bufferOrString) {
                        if (!Buffer.isBuffer(bufferOrString)) {
                            return new Buffer(String(bufferOrString), 'utf-8');
                        } else {
                            return bufferOrString;
                        }
                    }));
                    if ((typeof expectedResponseProperties.body === 'string' || (typeof expectedResponseProperties.body === 'object' && !Buffer.isBuffer(expectedResponseProperties.body))) || (typeof expectedResponseProperties.body === 'undefined' && bufferCanBeInterpretedAsUtf8(responseProperties.body) && isTextualContentType(res._headers['content-type']))) {
                        try {
                            responseProperties.body = responseProperties.body.toString('utf-8');
                        } catch (e) {
                            // The body cannot be intepreted as utf-8, keep it as a Buffer instance
                        }
                    }
                    if (/^application\/json\b/i.test(res._headers['content-type']) && typeof responseProperties.body === 'string' && (typeof expectedResponseProperties.body === 'undefined' || (typeof expectedResponseProperties.body === 'object' && !Buffer.isBuffer(expectedResponseProperties.body)))) {
                        responseProperties.body = JSON.parse(responseProperties.body);
                    } else if (Buffer.isBuffer(expectedResponseProperties.body) && (!responseProperties.body || typeof responseProperties.body === 'string')) {
                        responseProperties.body = new Buffer(responseProperties.body, 'utf-8');
                    }
                } else if (Buffer.isBuffer(expectedResponseProperties.body) && expectedResponseProperties.body.length === 0) {
                    responseProperties.body = new Buffer([]);
                } else if (expectedResponseProperties.body === '') {
                    responseProperties.body = '';
                }

                var httpResponse = new messy.HttpResponse({
                    headers: res._header || undefined,
                    body: responseProperties.body
                });

                var headersMatch = true;
                if (typeof expectedResponseProperties.headers !== 'undefined') {
                    var expectedResponseHeaders = new messy.Headers(expectedResponseProperties.headers),
                        headersThatMustNotBePresent = expectedResponseProperties.headers && typeof expectedResponseProperties.headers === 'object' && Object.keys(expectedResponseProperties.headers).filter(function (headerName) {
                            return typeof expectedResponseProperties.headers[headerName] === 'undefined';
                        });
                    delete expectedResponseProperties.headers;
                    if (expectedResponseHeaders.getNames().some(function (headerName) {
                        return !httpResponse.hasHeader(headerName, expectedResponseProperties.headers[headerName]);
                    })) {
                        headersMatch = false;
                    } else if (headersThatMustNotBePresent.some(function (headerName) {
                        return httpResponse.hasHeader(headerName);
                    })) {
                        headersMatch = false;
                    }
                }

                var bodyMatches = !('body' in expectedResponseProperties) || expect.equal(responseProperties.body, expectedResponseProperties.body);
                delete expectedResponseProperties.body;
                expect(responseProperties, 'to have properties', expectedResponseProperties);
                if (!headersMatch || !bodyMatches) {
                    expect.fail(expect.inspect(expect.output.clone(), httpResponse));
                }
            } catch (assertionError) {
                err = assertionError;
            }
            setImmediate(function () {
                done(err, context);
            });
        }
        ['write', 'end', 'destroy'].forEach(function (methodName) {
            var orig = res[methodName];
            res[methodName] = function (chunk, encoding) {
                var returnValue = orig.apply(this, arguments);
                if (methodName !== 'destroy') {
                    if (encoding && !/^utf-?8$/i.test(encoding)) {
                        throw new Error('encoding parameter not supported except for "utf-8": ' + encoding);
                    }
                    if (chunk) {
                        responseBodyChunks.push(chunk);
                    }
                }
                if (methodName === 'end' || methodName === 'destroy') {
                    isDestroyed = methodName === 'destroy';
                    doTheAssertions();
                }
                return returnValue;
            };
        });
        subject(req, res, next);
    });
};
<|endoftext|>


<commit_before>

var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map;

function initialize() {
  directionsDisplay = new google.maps.DirectionsRenderer();
  var USA = new google.maps.LatLng(38.2192743, -96.2644229);
  var mapOptions = {
    zoom:5,
    center: USA
  };
  map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
  directionsDisplay.setMap(map);
}

function calcRoute() {
  var start = document.getElementById('start').value;
  var end = document.getElementById('end').value;
  var request = {
      origin:start,
      destination:end,
      travelMode: google.maps.TravelMode.DRIVING
  };
  directionsService.route(request, function(response, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      directionsDisplay.setDirections(response);
      // array of latlngs on a given routet
      var routeLength = response.routes[0]["overview_path"].length;
      var lat = response.routes[0]["overview_path"][0].A;
      var lng = response.routes[0]["overview_path"][0].F;
      getWeather(lat, lng, routeLength);
    }
  });
}

function printWeather(temp, cond) {
  var message = "Current Weather: " + temp + " and " + cond;
  console.log(message);
}

//Print out error messages
function printError(error) {
  console.error(error.message);
}

function getWeather(lat, lng, routeLength) {
  // forecast.io API
  var url = "https://api.forecast.io/forecast/168b6b5a68104d52147285a01177bf63/" + lat + "," + lng + "?callback=?";
  // Exclude unneeded data blocks
  var opts = { exclude: "minutely,flags" }

  $.getJSON(url, opts).done(function(data) {
    try {
      if (routeLength >= 50)
      var temperature = Math.round(data.currently.temperature);
      var condition = data.currently.summary;

      printWeather(temperature, condition);

    } catch(error) {
      printError(error);
    }
      //printError({message: "There was an error getting the forecast for " + latitude + " " + longitude + ". (" + http.STATUS_CODES[res.statusCode] + ")"});
  }).fail(function(e) {
    console.log(e);
  });
}

google.maps.event.addDomListener(window, 'load', initialize);
<commit_msg>Route distance is now calculated<commit_after>

var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map;

var origin1;
var destinationA;

function initialize() {
  directionsDisplay = new google.maps.DirectionsRenderer();
  var USA = new google.maps.LatLng(38.2192743, -96.2644229);
  var mapOptions = {
    zoom:5,
    center: USA
  };
  map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
  directionsDisplay.setMap(map);
}

//-------------------- GET DISTANCE --------------------
function calculateDistances() {
  var service = new google.maps.DistanceMatrixService();
  service.getDistanceMatrix(
    {
      origins: [origin1],
      destinations: [destinationA],
      travelMode: google.maps.TravelMode.DRIVING,
      unitSystem: google.maps.UnitSystem.IMPERIAL,
      avoidHighways: false,
      avoidTolls: false
    }, callback);
}

function callback(response, status) {
  if (status != google.maps.DistanceMatrixStatus.OK) {
    alert('Error was: ' + status);
  } else {
    var origins = response.originAddresses;
    var destinations = response.destinationAddresses;

    for (var i = 0; i < origins.length; i++) {
      var results = response.rows[i].elements;
      for (var j = 0; j < results.length; j++) {
        console.log(results[j].distance.text)
      }
    }
  }
}

//-------------------- GET ROUTE --------------------

//get end lat and lng
//place all 4 coords in origin, destination vars
//call distance function
function calcRoute() {
  var start = document.getElementById('start').value;
  var end = document.getElementById('end').value;
  var request = {
      origin:start,
      destination:end,
      travelMode: google.maps.TravelMode.DRIVING
  };
  directionsService.route(request, function(response, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      directionsDisplay.setDirections(response);
      // array of latlngs on a given routet
      var routeLength = response.routes[0]["overview_path"].length;
      var startLat = response.routes[0]["overview_path"][0].A;
      var startLng = response.routes[0]["overview_path"][0].F;

      var pathArrayLength = response.routes[0]["overview_path"].length-1;
      var endLat = response.routes[0]["overview_path"][pathArrayLength].A;
      var endLng = response.routes[0]["overview_path"][pathArrayLength].F;

      origin1 = new google.maps.LatLng(startLat, startLng);
      destinationA = new google.maps.LatLng(endLat, endLng);

      getWeather(startLat, startLng, routeLength);
    }
  });
}

function printWeather(temp, cond) {
  var message = "Current Weather: " + temp + " and " + cond;
  console.log(message);
}

//Print out error messages
function printError(error) {
  console.error(error.message);
}

function getWeather(lat, lng, routeLength) {
  // forecast.io API
  var url = "https://api.forecast.io/forecast/168b6b5a68104d52147285a01177bf63/" + lat + "," + lng + "?callback=?";
  // Exclude unneeded data blocks
  var opts = { exclude: "minutely,flags" }

  $.getJSON(url, opts).done(function(data) {
    try {
      if (routeLength >= 50)
      var temperature = Math.round(data.currently.temperature);
      var condition = data.currently.summary;

      printWeather(temperature, condition);

    } catch(error) {
      printError(error);
    }
      //printError({message: "There was an error getting the forecast for " + latitude + " " + longitude + ". (" + http.STATUS_CODES[res.statusCode] + ")"});
  }).fail(function(e) {
    console.log(e);
  });
  // call to print distance in miles to the console
  calculateDistances();
  console.log(routeLength);
}

google.maps.event.addDomListener(window, 'load', initialize);
<|endoftext|>


<commit_before>const error_div_class = "form-group required company_class form-class has-feedback has-error";
const error_input_icon = "form-control col-md-6 glyphicon glyphicon-remove";
const ok_div_class = "form-group required company_class form-class has-feedback has-success";
const ok_input_icon = "form-control col-md-6 glyphicon glyphicon-ok";

$(function() {
  new WOW().init();

  var prod = window.location.hostname.indexOf("metronus.es") !== -1;

  $("#flag_es").click(function() {
    if(prod) Cookies.set('lang', 'es-es', {domain: '.metronus.es'});
    else Cookies.set('lang', 'es-es');
    location.reload(true);
  });

  $("#flag_en").click(function() {
    if(prod) Cookies.set('lang', 'en-us', {domain: '.metronus.es'});
    else Cookies.set('lang', 'en-us');
    location.reload(true);
  });

  // Cuando el AJAX da error pero se cambia, se quita el error del ajax para limpiar
  $("input").on('input', function(){
    if($(this).parent().find(".ajax-error").is(":visible")){
      $(this).parent().find(".ajax-error").hide();
      $(this).removeClass('glyphicon-remove')
    }
  });

  //Lor numericos
  $('.precio').each(function(i) {
    $(this).val($(this).attr("value").replace(",", "."));
  });
});

function initAjax(field, field_str, url){
  field.change(function(){
    ajaxValidation($(this), field_str, url);
  });
}

// It sets all the necessary to perform ajax validations
function ajaxValidation(field, field_str, url){
  var div = $(field).parent().parent();
  var msg_div = $(field).parent().find(".ajax-error");
  var data = { };

  // If the field is incorrect by Bootstrap Validator or is empty, it is just incorrect
  if(field.val() == "" || field.hasClass('glyphicon-remove')){
    return;
  }

  data[field_str] = field.val();

  $.ajax({
    url: url,
    data: data,
    dataType: 'json',
    success: function (data) {
      setValidationState(data.is_taken, div, msg_div);
    }
  });
}

// AUXILIAR FUNCTION TO SET VALIDATION STATE
function setValidationState(error, div, message){
  if(error){
    div.removeClass().addClass(error_div_class);
    div.find("input").removeClass().addClass(error_input_icon);
    div.find("i").removeClass("glyphicon-ok").addClass("glyphicon-remove");
    message.show();
  } else {
    div.removeClass().addClass(ok_div_class);
    div.find("input").removeClass().addClass(ok_input_icon);
    div.find("i").removeClass("glyphicon-remove").addClass("glyphicon-ok");
    message.hide();
  }
  div.find("input").removeClass("glyphicon");
}

// Collapse the last collapsible if there is more than one
function autocollapse() {
  var collapse_buttons = $("a.collapse-link");
  if(collapse_buttons.length > 1) {
      collapse_buttons.last().click();
  }
}

// Añadir el icono de info a los tooltips de información
$(function() {
  $("div.help-tooltip > p").first().prepend(" ");
  $("div.help-tooltip > p").slice(1).prepend(" ");
});
<commit_msg>Cambio en la manera de recarga de página al cambiar idioma para que no intente enviar forms de nuevo<commit_after>const error_div_class = "form-group required company_class form-class has-feedback has-error";
const error_input_icon = "form-control col-md-6 glyphicon glyphicon-remove";
const ok_div_class = "form-group required company_class form-class has-feedback has-success";
const ok_input_icon = "form-control col-md-6 glyphicon glyphicon-ok";

$(function() {
  new WOW().init();

  var prod = window.location.hostname.indexOf("metronus.es") !== -1;

  $("#flag_es").click(function() {
    if(prod) Cookies.set('lang', 'es-es', {domain: '.metronus.es'});
    else Cookies.set('lang', 'es-es');
    window.location.href = window.location.href;
  });

  $("#flag_en").click(function() {
    if(prod) Cookies.set('lang', 'en-us', {domain: '.metronus.es'});
    else Cookies.set('lang', 'en-us');
    window.location.href = window.location.href;
  });

  // Cuando el AJAX da error pero se cambia, se quita el error del ajax para limpiar
  $("input").on('input', function(){
    if($(this).parent().find(".ajax-error").is(":visible")){
      $(this).parent().find(".ajax-error").hide();
      $(this).removeClass('glyphicon-remove')
    }
  });

  //Lor numericos
  $('.precio').each(function(i) {
    $(this).val($(this).attr("value").replace(",", "."));
  });
});

function initAjax(field, field_str, url){
  field.change(function(){
    ajaxValidation($(this), field_str, url);
  });
}

// It sets all the necessary to perform ajax validations
function ajaxValidation(field, field_str, url){
  var div = $(field).parent().parent();
  var msg_div = $(field).parent().find(".ajax-error");
  var data = { };

  // If the field is incorrect by Bootstrap Validator or is empty, it is just incorrect
  if(field.val() == "" || field.hasClass('glyphicon-remove')){
    return;
  }

  data[field_str] = field.val();

  $.ajax({
    url: url,
    data: data,
    dataType: 'json',
    success: function (data) {
      setValidationState(data.is_taken, div, msg_div);
    }
  });
}

// AUXILIAR FUNCTION TO SET VALIDATION STATE
function setValidationState(error, div, message){
  if(error){
    div.removeClass().addClass(error_div_class);
    div.find("input").removeClass().addClass(error_input_icon);
    div.find("i").removeClass("glyphicon-ok").addClass("glyphicon-remove");
    message.show();
  } else {
    div.removeClass().addClass(ok_div_class);
    div.find("input").removeClass().addClass(ok_input_icon);
    div.find("i").removeClass("glyphicon-remove").addClass("glyphicon-ok");
    message.hide();
  }
  div.find("input").removeClass("glyphicon");
}

// Collapse the last collapsible if there is more than one
function autocollapse() {
  var collapse_buttons = $("a.collapse-link");
  if(collapse_buttons.length > 1) {
      collapse_buttons.last().click();
  }
}

// Añadir el icono de info a los tooltips de información
$(function() {
  $("div.help-tooltip > p").first().prepend(" ");
  $("div.help-tooltip > p").slice(1).prepend(" ");
});
<|endoftext|>


<commit_before>import testSanityClient from '../../helpers/sanityClientSetUp'

let testDocumentId = ''

function setUpSuite() {
  cy.login()

  testSanityClient.create(doc).then((res) => {
    testDocumentId = res._id
    cy.visit(`/test/desk/input-ci;conditionalFieldset;${res._id}%2Csince%3D%40lastPublished`)
  })
}

function tearDownSuite() {
  testSanityClient.delete(testDocumentId)
}

const doc = {
  _type: 'conditionalFieldset',
  title: 'Conditional fieldset [Cypress]',
  hidden: true,
  readOnly: true,
  // Multi
  multiHiddenBooleanFalse1: 'Lorem',
  multiHiddenBooleanFalse2: 'Lorem',
  multiHiddenBooleanTrue1: 'Lorem',
  multiHiddenBooleanTrue2: 'Lorem',
  multiHiddenCallbackFalse1: 'Lorem',
  multiHiddenCallbackFalse2: 'Lorem',
  multiHiddenCallbackTrue1: 'Lorem',
  multiHiddenCallbackTrue2: 'Lorem',
  multiReadOnlyBooleanFalse1: 'Lorem',
  multiReadOnlyBooleanFalse2: 'Lorem',
  multiReadOnlyBooleanTrue1: 'Lorem',
  multiReadOnlyBooleanTrue2: 'Lorem',
  multiReadOnlyCallbackFalse1: 'Lorem',
  multiReadOnlyCallbackFalse2: 'Lorem',
  multiReadOnlyCallbackTrue1: 'Lorem',
  multiReadOnlyCallbackTrue2: 'Lorem',
  // Single
  singleHiddenBooleanFalse1: 'Lorem',
  singleHiddenBooleanTrue1: 'Lorem',
  singleHiddenCallbackFalse1: 'Lorem',
  singleHiddenCallbackTrue1: 'Lorem',
  singleReadOnlyBooleanFalse1: 'Lorem',
  singleReadOnlyBooleanTrue1: 'Lorem',
  singleReadOnlyCallbackFalse1: 'Lorem',
  singleReadOnlyCallbackTrue1: 'Lorem',
}

describe('@sanity/field: Multi fieldset and review changes', () => {
  before(() => {
    setUpSuite()
  })

  beforeEach(() => {
    cy.viewport(2000, 3500)
  })

  // Hidden boolean false
  it('when hidden property is false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenBooleanFalse"]').should('be.visible')
  })

  //Hidden boolean true
  it('when hidden property is true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenBooleanTrue"]').should('not.exist')
  })

  //Hidden callback (document) false
  it('when the hidden callback (document) returns false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenCallbackFalse"]').should('be.visible')
  })

  //Hidden callback (document) true
  it('when the hidden callback (document) returns true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenCallbackTrue"]').should('not.exist')
  })

  //Read only boolean false
  it('when the readOnly property is false, the fieldset is enabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyBooleanFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  //Read only boolean true
  it('when the readOnly property is true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyBooleanTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  //Read only callback (document) false
  it('when the readOnly callback (document) returns false, the fieldset is enabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyCallbackFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  //Read only callback (document) true
  it('when the readOnly callback (document) returns true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyCallbackTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns true, the fieldset is disabled
  - when readOnly callback (currentUser) returns false, the fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the fieldset is disabled
  - when readOnly callback (value) returns false, the fieldset is not disabled
  - when readOnly callback (value) returns true, the fieldset is disabled
  - when hidden callback (parent) returns true, the fieldset is not rendered
  - when hidden callback (parent) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns true, the fieldset is not rendered
  - when hidden callback (value) returns false, the fieldset is rendered
  - when hidden callback (value) returns true, the fieldset is not rendered
  */

  /* --- Review Changes --- */

  // Hidden boolean false
  it('when hidden property is false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenBooleanFalse"]').should('be.visible')
  })

  // Hidden boolean true
  it('when hidden property is true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenBooleanTrue"]').should('not.exist')
  })

  //Hidden callback (document) false
  it('when the hidden callback (document) returns false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenCallbackFalse"]').should('be.visible')
  })

  //Hidden callback (document) true
  it('when the hidden callback (document) returns true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenCallbackTrue"]').should('not.exist')
  })

  //Read only boolean false
  it('when the readOnly property is false, the review change for fieldset is enabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyBooleanFalse"]').should(
      'not.be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanFalse1"').should(
      'not.be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanFalse2"').should(
      'not.be.disabled'
    )
  })

  //Read only boolean true
  it('when the readOnly property is true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyBooleanTrue"]').should(
      'be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanTrue1"').should(
      'be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanTrue2"').should(
      'be.disabled'
    )
  })

  //Read only callback (document) false
  it('when the readOnly callback (document) returns false, the review change for fieldset is enabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyCallbackFalse"]').should(
      'not.be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackFalse1"').should(
      'not.be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackFalse2"').should(
      'not.be.disabled'
    )
  })

  //Read only callback (document) true
  it('when the readOnly callback (document) returns true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyCallbackTrue"]').should(
      'be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackTrue1"').should(
      'be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackTrue2"').should(
      'be.disabled'
    )
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO

  - when readOnly callback (parent) returns false, the review change for fieldset is not disabled
  - when readOnly callback (parent) returns true, the review change for fieldset is disabled
  - when readOnly callback (currentUser) returns false, the review change for fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the review change for fieldset is disabled
  - when readOnly callback (value) returns false, the review change for fieldset is not disabled
  - when readOnly callback (value) returns true, the review change for fieldset is disabled
  - when hidden callback (parent) returns true, the review change for fieldset is not rendered
  - when hidden callback (parent) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns true, the review change for fieldset is not rendered
  - when hidden callback (value) returns false, the review change for fieldset is rendered
  - when hidden callback (value) returns true, the review change for fieldset is not rendered'
  */

  after(() => {
    tearDownSuite()
  })
})

describe('@sanity/field: Single fieldset and review changes', () => {
  before(() => {
    setUpSuite()
  })

  beforeEach(() => {
    cy.viewport(2000, 3500)
  })

  //Hidden boolean false
  it('when hidden property is false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenBooleanFalse"]').should('be.visible')
  })

  //Hidden boolean true
  it('when hidden property is true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenBooleanTrue"]').should('not.exist')
  })

  //Hidden callback (document) false
  it('when hidden callback (document) returns false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenCallbackFalse"]').should('be.visible')
  })

  //Hidden callback (document) true
  it('when hidden callback (document) returns true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenCallbackTrue"]').should('not.exist')
  })

  //Read only boolean false
  it('when readOnly property is false, the fieldset is enabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyBooleanFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  //Read only boolean true
  it('when readOnly property is true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyBooleanTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  //Read only callback (document) false
  it('when readOnly callback (document) returns false, the fieldset is not disabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyCallbackFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  //Read only callback (document) true
  it('when readOnly callback (document) returns true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyCallbackTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns true, the fieldset is disabled
  - when readOnly callback (currentUser) returns false, the fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the fieldset is disabled
  - when readOnly callback (value) returns false, the fieldset is not disabled
  - when readOnly callback (value) returns true, the fieldset is disabled
  - when hidden callback (parent) returns true, the fieldset is not rendered
  - when hidden callback (parent) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns true, the fieldset is not rendered
  - when hidden callback (value) returns false, the fieldset is rendered
  - when hidden callback (value) returns true, the fieldset is not rendered
  */

  /* --- Review Changes --- */

  //Hidden boolean false
  it('when hidden property is false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenBooleanFalse1"').should(
      'be.visible'
    )
  })

  //Hidden boolean true
  it('when hidden property is true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenBooleanTrue1"').should(
      'not.exist'
    )
  })

  //Hidden callback (document) false
  it('when hidden callback (document) returns false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenCallbackFalse1"').should(
      'be.visible'
    )
  })

  //Hidden callback (document) true
  it('when hidden callback (document) returns true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenCallbackTrue1"').should(
      'not.exist'
    )
  })

  //Read only boolean false
  it('when readOnly property is false, the review change for fieldset is enabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyBooleanFalse1"').should(
      'not.be.disabled'
    )
  })

  //Read only boolean true
  it('when readOnly property is true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyBooleanTrue1"').should(
      'be.disabled'
    )
  })

  //Read only callback (document) false
  it('when readOnly callback (document) (document) returns false, the review change for fieldset is not disabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyCallbackFalse1"').should(
      'not.be.disabled'
    )
  })

  //Read only callback (document) true
  it('when readOnly callback (document) returns true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyCallbackTrue1"').should(
      'be.disabled'
    )
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO

  - when readOnly callback (parent) returns false, the review change for fieldset is not disabled
  - when readOnly callback (parent) returns true, the review change for fieldset is disabled
  - when readOnly callback (currentUser) returns false, the review change for fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the review change for fieldset is disabled
  - when readOnly callback (value) returns false, the review change for fieldset is not disabled
  - when readOnly callback (value) returns true, the review change for fieldset is disabled
  - when hidden callback (parent) returns true, the review change for fieldset is not rendered
  - when hidden callback (parent) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns true, the review change for fieldset is not rendered
  - when hidden callback (value) returns false, the review change for fieldset is rendered
  - when hidden callback (value) returns true, the review change for fieldset is not rendered'
  */

  after(() => {
    tearDownSuite()
  })
})
<commit_msg>test: update document creation and navigation (#2972)<commit_after>import testSanityClient from '../../helpers/sanityClientSetUp'

const testDocumentId = 'conditional-fieldset-test'
const testLocation = `/test/desk/input-ci;conditionalFieldset;${testDocumentId}%2Csince%3D%40lastPublished`

const doc = {
  _id: testDocumentId,
  _type: 'conditionalFieldset',
  title: 'Conditional fieldset [Cypress]',
  hidden: true,
  readOnly: true,
  // Multi
  multiHiddenBooleanFalse1: 'Lorem',
  multiHiddenBooleanFalse2: 'Lorem',
  multiHiddenBooleanTrue1: 'Lorem',
  multiHiddenBooleanTrue2: 'Lorem',
  multiHiddenCallbackFalse1: 'Lorem',
  multiHiddenCallbackFalse2: 'Lorem',
  multiHiddenCallbackTrue1: 'Lorem',
  multiHiddenCallbackTrue2: 'Lorem',
  multiReadOnlyBooleanFalse1: 'Lorem',
  multiReadOnlyBooleanFalse2: 'Lorem',
  multiReadOnlyBooleanTrue1: 'Lorem',
  multiReadOnlyBooleanTrue2: 'Lorem',
  multiReadOnlyCallbackFalse1: 'Lorem',
  multiReadOnlyCallbackFalse2: 'Lorem',
  multiReadOnlyCallbackTrue1: 'Lorem',
  multiReadOnlyCallbackTrue2: 'Lorem',
  // Single
  singleHiddenBooleanFalse1: 'Lorem',
  singleHiddenBooleanTrue1: 'Lorem',
  singleHiddenCallbackFalse1: 'Lorem',
  singleHiddenCallbackTrue1: 'Lorem',
  singleReadOnlyBooleanFalse1: 'Lorem',
  singleReadOnlyBooleanTrue1: 'Lorem',
  singleReadOnlyCallbackFalse1: 'Lorem',
  singleReadOnlyCallbackTrue1: 'Lorem',
}

describe('@sanity/field: Multi fieldset and review changes', () => {
  before(async () => {
    await testSanityClient.createOrReplace(doc)
  })

  beforeEach(() => {
    cy.viewport(2000, 3500)
    cy.visit(testLocation)
  })

  // Hidden boolean false
  it('when hidden property is false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenBooleanFalse"]').should('be.visible')
  })

  // Hidden boolean true
  it('when hidden property is true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenBooleanTrue"]').should('not.exist')
  })

  // Hidden callback (document) false
  it('when the hidden callback (document) returns false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenCallbackFalse"]').should('be.visible')
  })

  // Hidden callback (document) true
  it('when the hidden callback (document) returns true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-multiHiddenCallbackTrue"]').should('not.exist')
  })

  // Read only boolean false
  it('when the readOnly property is false, the fieldset is enabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyBooleanFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  // Read only boolean true
  it('when the readOnly property is true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyBooleanTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  // Read only callback (document) false
  it('when the readOnly callback (document) returns false, the fieldset is enabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyCallbackFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  // Read only callback (document) true
  it('when the readOnly callback (document) returns true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-multiReadOnlyCallbackTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns true, the fieldset is disabled
  - when readOnly callback (currentUser) returns false, the fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the fieldset is disabled
  - when readOnly callback (value) returns false, the fieldset is not disabled
  - when readOnly callback (value) returns true, the fieldset is disabled
  - when hidden callback (parent) returns true, the fieldset is not rendered
  - when hidden callback (parent) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns true, the fieldset is not rendered
  - when hidden callback (value) returns false, the fieldset is rendered
  - when hidden callback (value) returns true, the fieldset is not rendered
  */

  /* --- Review Changes --- */

  // Hidden boolean false
  it('when hidden property is false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenBooleanFalse"]').should('be.visible')
  })

  // Hidden boolean true
  it('when hidden property is true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenBooleanTrue"]').should('not.exist')
  })

  // Hidden callback (document) false
  it('when the hidden callback (document) returns false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenCallbackFalse"]').should('be.visible')
  })

  // Hidden callback (document) true
  it('when the hidden callback (document) returns true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="group-change-multiHiddenCallbackTrue"]').should('not.exist')
  })

  // Read only boolean false
  it('when the readOnly property is false, the review change for fieldset is enabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyBooleanFalse"]').should(
      'not.be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanFalse1"').should(
      'not.be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanFalse2"').should(
      'not.be.disabled'
    )
  })

  // Read only boolean true
  it('when the readOnly property is true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyBooleanTrue"]').should(
      'be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanTrue1"').should(
      'be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyBooleanTrue2"').should(
      'be.disabled'
    )
  })

  // Read only callback (document) false
  it('when the readOnly callback (document) returns false, the review change for fieldset is enabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyCallbackFalse"]').should(
      'not.be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackFalse1"').should(
      'not.be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackFalse2"').should(
      'not.be.disabled'
    )
  })

  // Read only callback (document) true
  it('when the readOnly callback (document) returns true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="group-change-revert-button-multiReadOnlyCallbackTrue"]').should(
      'be.disabled'
    )

    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackTrue1"').should(
      'be.disabled'
    )
    cy.get('[data-testid="single-change-revert-button-multiReadOnlyCallbackTrue2"').should(
      'be.disabled'
    )
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO

  - when readOnly callback (parent) returns false, the review change for fieldset is not disabled
  - when readOnly callback (parent) returns true, the review change for fieldset is disabled
  - when readOnly callback (currentUser) returns false, the review change for fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the review change for fieldset is disabled
  - when readOnly callback (value) returns false, the review change for fieldset is not disabled
  - when readOnly callback (value) returns true, the review change for fieldset is disabled
  - when hidden callback (parent) returns true, the review change for fieldset is not rendered
  - when hidden callback (parent) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns true, the review change for fieldset is not rendered
  - when hidden callback (value) returns false, the review change for fieldset is rendered
  - when hidden callback (value) returns true, the review change for fieldset is not rendered'
  */
})

describe('@sanity/field: Single fieldset and review changes', () => {
  before(async () => {
    await testSanityClient.createOrReplace(doc)
  })

  beforeEach(() => {
    cy.viewport(2000, 3500)
    cy.visit(testLocation)
  })

  // Hidden boolean false
  it('when hidden property is false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenBooleanFalse"]').should('be.visible')
  })

  // Hidden boolean true
  it('when hidden property is true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenBooleanTrue"]').should('not.exist')
  })

  // Hidden callback (document) false
  it('when hidden callback (document) returns false, the fieldset is rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenCallbackFalse"]').should('be.visible')
  })

  // Hidden callback (document) true
  it('when hidden callback (document) returns true, the fieldset is not rendered', () => {
    cy.get('[data-testid="fieldset-singleHiddenCallbackTrue"]').should('not.exist')
  })

  // Read only boolean false
  it('when readOnly property is false, the fieldset is enabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyBooleanFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  // Read only boolean true
  it('when readOnly property is true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyBooleanTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  // Read only callback (document) false
  it('when readOnly callback (document) returns false, the fieldset is not disabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyCallbackFalse"]')
      .find('input')
      .should('not.have.attr', 'readonly')
  })

  // Read only callback (document) true
  it('when readOnly callback (document) returns true, the fieldset is disabled', () => {
    cy.get('[data-testid="fieldset-singleReadOnlyCallbackTrue"]')
      .find('input')
      .should('have.attr', 'readonly')
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns false, the fieldset is not disabled
  - when readOnly callback (parent) returns true, the fieldset is disabled
  - when readOnly callback (currentUser) returns false, the fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the fieldset is disabled
  - when readOnly callback (value) returns false, the fieldset is not disabled
  - when readOnly callback (value) returns true, the fieldset is disabled
  - when hidden callback (parent) returns true, the fieldset is not rendered
  - when hidden callback (parent) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns false, the fieldset is rendered
  - when hidden callback (currentUser) returns true, the fieldset is not rendered
  - when hidden callback (value) returns false, the fieldset is rendered
  - when hidden callback (value) returns true, the fieldset is not rendered
  */

  /* --- Review Changes --- */

  // Hidden boolean false
  it('when hidden property is false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenBooleanFalse1"').should(
      'be.visible'
    )
  })

  // Hidden boolean true
  it('when hidden property is true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenBooleanTrue1"').should(
      'not.exist'
    )
  })

  // Hidden callback (document) false
  it('when hidden callback (document) returns false, the review change for fieldset is rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenCallbackFalse1"').should(
      'be.visible'
    )
  })

  // Hidden callback (document) true
  it('when hidden callback (document) returns true, the review change for fieldset is not rendered', () => {
    cy.get('[data-testid="single-change-revert-button-singleHiddenCallbackTrue1"').should(
      'not.exist'
    )
  })

  // Read only boolean false
  it('when readOnly property is false, the review change for fieldset is enabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyBooleanFalse1"').should(
      'not.be.disabled'
    )
  })

  // Read only boolean true
  it('when readOnly property is true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyBooleanTrue1"').should(
      'be.disabled'
    )
  })

  // Read only callback (document) false
  it('when readOnly callback (document) (document) returns false, the review change for fieldset is not disabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyCallbackFalse1"').should(
      'not.be.disabled'
    )
  })

  // Read only callback (document) true
  it('when readOnly callback (document) returns true, the review change for fieldset is disabled', () => {
    cy.get('[data-testid="single-change-revert-button-singleReadOnlyCallbackTrue1"').should(
      'be.disabled'
    )
  })

  // eslint-disable-next-line no-warning-comments
  /* @TODO

  - when readOnly callback (parent) returns false, the review change for fieldset is not disabled
  - when readOnly callback (parent) returns true, the review change for fieldset is disabled
  - when readOnly callback (currentUser) returns false, the review change for fieldset is not disabled
  - when readOnly callback (currentUser) returns true, the review change for fieldset is disabled
  - when readOnly callback (value) returns false, the review change for fieldset is not disabled
  - when readOnly callback (value) returns true, the review change for fieldset is disabled
  - when hidden callback (parent) returns true, the review change for fieldset is not rendered
  - when hidden callback (parent) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns false, the review change for fieldset is rendered
  - when hidden callback (currentUser) returns true, the review change for fieldset is not rendered
  - when hidden callback (value) returns false, the review change for fieldset is rendered
  - when hidden callback (value) returns true, the review change for fieldset is not rendered'
  */
})
<|endoftext|>


<commit_before>module.exports = function() {
  'use strict';

  var isNode = typeof module != 'undefined' && typeof module.exports != 'undefined',
      events = isNode ? require('events-manager').EventsManager : window.EventsManager;

  DependencyInjection.model('$AbstractModel', [function() {
    return function AbstractModel(modelName, nodeConstructor, browserConstructor) {

      var model;

      if (isNode && nodeConstructor) {
        DependencyInjection.injector.model.invoke(null, nodeConstructor, {
          model: {
            $returnMongoModel: function() {
              return function(databaseInstance, schema) {
                model = databaseInstance.model(modelName, schema);
              };
            },

            $returnSQLModel: function() {
              return function(databaseInstance, schema, options) {
                model = databaseInstance.define(modelName, schema, options);
              };
            },

            $returnModel: function() {
              return function(returnModel) {
                model = returnModel;
              };
            }
          }
        });
      }
      else if (!isNode && browserConstructor) {
        model = function(config) {

          var _this = this;

          events.call(this);

          DependencyInjection.injector.model.invoke(null, browserConstructor, {
            model: {
              $this: function() {
                return _this;
              },

              $config: function() {
                return config;
              }
            }
          });
        };
      }

      return model;
    };
  }]);

};
<commit_msg>fix(common abstract model): adapt to the waterline service<commit_after>module.exports = function() {
  'use strict';

  var isNode = typeof module != 'undefined' && typeof module.exports != 'undefined',
      events = isNode ? require('events-manager').EventsManager : window.EventsManager;

  DependencyInjection.model('$AbstractModel', [function() {
    return function AbstractModel(name, nodeConstructor, browserConstructor) {

      if (isNode && nodeConstructor) {
        var Waterline = require('waterline'),
            injectorModel = DependencyInjection.injector.model,
            $DatabaseService = injectorModel.get('$DatabaseService'),
            config = injectorModel.invoke(null, nodeConstructor);

        if (!config || !config.identity) {
          return console.log(new Error('Missing "identity" property in schema'));
        }

        var model = Waterline.Collection.extend(config);

        $DatabaseService.loadModel(name, config.identity, model);

        return model;
      }
      else if (!isNode && browserConstructor) {
        return function(config) {

          var _this = this;

          events.call(this);

          DependencyInjection.injector.model.invoke(null, browserConstructor, {
            model: {
              $this: function() {
                return _this;
              },

              $config: function() {
                return config;
              }
            }
          });
        };
      }
    };
  }]);

};
<|endoftext|>


<commit_before>var traverse = require("../../traverse");
var util     = require("../../util");
var t        = require("../../types");

exports.ClassDeclaration = function (node, parent, file, scope) {
  return new Class(node, file, scope, false).run();
};

exports.ClassExpression = function (node, parent, file, scope) {
  return new Class(node, file, scope, true).run();
};

/**
 * Description
 *
 * @param {Node} node
 * @param {File} file
 * @param {Scope} scope
 * @param {Boolean} closure
 */

function Class(node, file, scope, closure) {
  this.closure = closure;
  this.scope   = scope;
  this.node    = node;
  this.file    = file;

  this.hasInstanceMutators = false;
  this.hasStaticMutators   = false;

  this.instanceMutatorMap = {};
  this.staticMutatorMap   = {};
  this.hasConstructor     = false;
  this.className          = node.id || file.generateUidIdentifier("class", scope);
  this.superName          = node.superClass;
}

/**
 * Description
 *
 * @returns {Array}
 */

Class.prototype.run = function () {
  var superName = this.superName;
  var className = this.className;
  var file      = this.file;

  //

  var body = this.body = [];

  var constructor = t.functionExpression(null, [], t.blockStatement([]));
  if (this.node.id) constructor.id = className;
  this.constructor = constructor;

  body.push(t.variableDeclaration("let", [
    t.variableDeclarator(className, constructor)
  ]));

  //

  if (superName && t.isDynamic(superName)) {
    // so we're only evaluating it once
    var superRefName = "super";
    if (className) superRefName = className.name + "Super";

    var superRef = file.generateUidIdentifier(superRefName, this.scope);
    body.unshift(t.variableDeclaration("var", [
      t.variableDeclarator(superRef, superName)
    ]));
    superName = superRef;
  }

  this.superName = superName;

  //

  if (superName) {
    body.push(t.expressionStatement(t.callExpression(file.addDeclaration("extends"), [className, superName])));
  }

  this.buildBody();

  if (this.closure) {
    if (body.length === 1) {
      // only a constructor so no need for a closure container
      return constructor;
    } else {
      body.push(t.returnStatement(className));
      return t.callExpression(
        t.functionExpression(null, [], t.blockStatement(body)),
        []
      );
    }
  } else {
    return body;
  }
};

/**
 * Description
 */

Class.prototype.buildBody = function () {
  var constructor = this.constructor;
  var className   = this.className;
  var superName   = this.superName;
  var classBody   = this.node.body.body;
  var body        = this.body;
  var self        = this;

  for (var i in classBody) {
    var node = classBody[i];
    if (t.isMethodDefinition(node)) {
      self.replaceInstanceSuperReferences(node);

      if (node.key.name === "constructor") {
        self.pushConstructor(node);
      } else {
        self.pushMethod(node);
      }
    } else if (t.isPrivateDeclaration(node)) {
      self.closure = true;
      body.unshift(node);
    }
  }

  if (!this.hasConstructor && superName) {
    constructor.body.body.push(util.template("class-super-constructor-call", {
      SUPER_NAME: superName
    }, true));
  }

  var instanceProps;
  var staticProps;

  if (this.hasInstanceMutators) {
    var protoId = util.template("prototype-identifier", {
      CLASS_NAME: className
    });

    instanceProps = util.buildDefineProperties(this.instanceMutatorMap, protoId);
  }

  if (this.hasStaticMutators) {
    staticProps = util.buildDefineProperties(this.staticMutatorMap, className);
  }

  if (instanceProps || staticProps) {
    staticProps = staticProps || t.literal(null);

    var args = [className, staticProps];
    if (instanceProps) args.push(instanceProps);

    body.push(t.expressionStatement(
      t.callExpression(this.file.addDeclaration("class-props"), args)
    ));
  }
};

/**
 * Push a method to it's respective mutatorMap.
 *
 * @param {Node} node MethodDefinition
 */

Class.prototype.pushMethod = function (node) {
  var methodName = node.key;

  var kind = node.kind;

  if (kind === "") {
    // method

    var className = this.className;
    if (!node.static) className = t.memberExpression(className, t.identifier("prototype"));
    methodName = t.memberExpression(className, methodName, node.computed);

    var expr = t.expressionStatement(t.assignmentExpression("=", methodName, node.value));
    t.inheritsComments(expr, node);
    this.body.push(expr);
  } else {
    // mutator
    var mutatorMap = this.instanceMutatorMap;
    if (node.static) {
      this.hasStaticMutators = true;
      mutatorMap = this.staticMutatorMap;
    } else {
      this.hasInstanceMutators = true;
    }
    util.pushMutatorMap(mutatorMap, methodName, kind, node);
  }
};

/**
 * Given a `methodNode`, produce a `MemberExpression` super class reference.
 *
 * @param {Node} methodNode MethodDefinition
 * @param {Node} node Identifier
 * @param {Node} parent
 *
 * @returns {Node}
 */

Class.prototype.superIdentifier = function (methodNode, id, parent) {
  var methodName = methodNode.key;
  var superName  = this.superName || t.identifier("Function");

  if (parent.property === id) {
    return;
  } else if (t.isCallExpression(parent, { callee: id })) {
    // super(); -> ClassName.prototype.MethodName.call(this);
    parent.arguments.unshift(t.thisExpression());

    if (methodName.name === "constructor") {
      // constructor() { super(); }
      return t.memberExpression(superName, t.identifier("call"));
    } else {
      id = superName;

      // foo() { super(); }
      if (!methodNode.static) {
        id = t.memberExpression(id, t.identifier("prototype"));
      }

      id = t.memberExpression(id, methodName, methodNode.computed);
      return t.memberExpression(id, t.identifier("call"));
    }
  } else if (t.isMemberExpression(parent) && !methodNode.static) {
    // super.test -> ClassName.prototype.test
    return t.memberExpression(superName, t.identifier("prototype"));
  } else {
    return superName;
  }
};

/**
 * Replace all `super` references with a reference to our `superClass`.
 *
 * @param {Node} methodNode MethodDefinition
 */

Class.prototype.replaceInstanceSuperReferences = function (methodNode) {
  var method = methodNode.value;
  var self = this;

  traverse(method, function (node, parent) {
    if (t.isIdentifier(node, { name: "super" })) {
      return self.superIdentifier(methodNode, node, parent);
    } else if (t.isCallExpression(node)) {
      var callee = node.callee;
      if (!t.isMemberExpression(callee)) return;
      if (callee.object.name !== "super") return;

      // super.test(); -> ClassName.prototype.MethodName.call(this);
      callee.property = t.memberExpression(callee.property, t.identifier("call"));
      node.arguments.unshift(t.thisExpression());
    }
  });
};

/**
 * Replace the constructor body of our class.
 *
 * @param {Node} method MethodDefinition
 */

Class.prototype.pushConstructor = function (method) {
  if (method.kind !== "") {
    throw this.file.errorWithNode(method, "illegal kind for constructor method");
  }

  var construct = this.constructor;
  var fn        = method.value;

  this.hasConstructor = true;

  t.inherits(construct, fn);
  t.inheritsComments(construct, method);

  construct.defaults = fn.defaults;
  construct.params   = fn.params;
  construct.body     = fn.body;
  construct.rest     = fn.rest;
};
<commit_msg>make first node in a class body inherit class comments<commit_after>var traverse = require("../../traverse");
var util     = require("../../util");
var t        = require("../../types");

exports.ClassDeclaration = function (node, parent, file, scope) {
  return new Class(node, file, scope, false).run();
};

exports.ClassExpression = function (node, parent, file, scope) {
  return new Class(node, file, scope, true).run();
};

/**
 * Description
 *
 * @param {Node} node
 * @param {File} file
 * @param {Scope} scope
 * @param {Boolean} closure
 */

function Class(node, file, scope, closure) {
  this.closure = closure;
  this.scope   = scope;
  this.node    = node;
  this.file    = file;

  this.hasInstanceMutators = false;
  this.hasStaticMutators   = false;

  this.instanceMutatorMap = {};
  this.staticMutatorMap   = {};
  this.hasConstructor     = false;
  this.className          = node.id || file.generateUidIdentifier("class", scope);
  this.superName          = node.superClass;
}

/**
 * Description
 *
 * @returns {Array}
 */

Class.prototype.run = function () {
  var superName = this.superName;
  var className = this.className;
  var file      = this.file;

  //

  var body = this.body = [];

  var constructor = t.functionExpression(null, [], t.blockStatement([]));
  if (this.node.id) constructor.id = className;
  this.constructor = constructor;

  body.push(t.variableDeclaration("let", [
    t.variableDeclarator(className, constructor)
  ]));

  //

  if (superName && t.isDynamic(superName)) {
    // so we're only evaluating it once
    var superRefName = "super";
    if (className) superRefName = className.name + "Super";

    var superRef = file.generateUidIdentifier(superRefName, this.scope);
    body.unshift(t.variableDeclaration("var", [
      t.variableDeclarator(superRef, superName)
    ]));
    superName = superRef;
  }

  this.superName = superName;

  //

  if (superName) {
    body.push(t.expressionStatement(t.callExpression(file.addDeclaration("extends"), [className, superName])));
  }

  this.buildBody();

  t.inheritsComments(body[0], this.node);

  if (this.closure) {
    if (body.length === 1) {
      // only a constructor so no need for a closure container
      return constructor;
    } else {
      body.push(t.returnStatement(className));
      return t.callExpression(
        t.functionExpression(null, [], t.blockStatement(body)),
        []
      );
    }
  } else {
    return body;
  }
};

/**
 * Description
 */

Class.prototype.buildBody = function () {
  var constructor = this.constructor;
  var className   = this.className;
  var superName   = this.superName;
  var classBody   = this.node.body.body;
  var body        = this.body;
  var self        = this;

  for (var i in classBody) {
    var node = classBody[i];
    if (t.isMethodDefinition(node)) {
      self.replaceInstanceSuperReferences(node);

      if (node.key.name === "constructor") {
        self.pushConstructor(node);
      } else {
        self.pushMethod(node);
      }
    } else if (t.isPrivateDeclaration(node)) {
      self.closure = true;
      body.unshift(node);
    }
  }

  if (!this.hasConstructor && superName) {
    constructor.body.body.push(util.template("class-super-constructor-call", {
      SUPER_NAME: superName
    }, true));
  }

  var instanceProps;
  var staticProps;

  if (this.hasInstanceMutators) {
    var protoId = util.template("prototype-identifier", {
      CLASS_NAME: className
    });

    instanceProps = util.buildDefineProperties(this.instanceMutatorMap, protoId);
  }

  if (this.hasStaticMutators) {
    staticProps = util.buildDefineProperties(this.staticMutatorMap, className);
  }

  if (instanceProps || staticProps) {
    staticProps = staticProps || t.literal(null);

    var args = [className, staticProps];
    if (instanceProps) args.push(instanceProps);

    body.push(t.expressionStatement(
      t.callExpression(this.file.addDeclaration("class-props"), args)
    ));
  }
};

/**
 * Push a method to it's respective mutatorMap.
 *
 * @param {Node} node MethodDefinition
 */

Class.prototype.pushMethod = function (node) {
  var methodName = node.key;

  var kind = node.kind;

  if (kind === "") {
    // method

    var className = this.className;
    if (!node.static) className = t.memberExpression(className, t.identifier("prototype"));
    methodName = t.memberExpression(className, methodName, node.computed);

    var expr = t.expressionStatement(t.assignmentExpression("=", methodName, node.value));
    t.inheritsComments(expr, node);
    this.body.push(expr);
  } else {
    // mutator
    var mutatorMap = this.instanceMutatorMap;
    if (node.static) {
      this.hasStaticMutators = true;
      mutatorMap = this.staticMutatorMap;
    } else {
      this.hasInstanceMutators = true;
    }
    util.pushMutatorMap(mutatorMap, methodName, kind, node);
  }
};

/**
 * Given a `methodNode`, produce a `MemberExpression` super class reference.
 *
 * @param {Node} methodNode MethodDefinition
 * @param {Node} node Identifier
 * @param {Node} parent
 *
 * @returns {Node}
 */

Class.prototype.superIdentifier = function (methodNode, id, parent) {
  var methodName = methodNode.key;
  var superName  = this.superName || t.identifier("Function");

  if (parent.property === id) {
    return;
  } else if (t.isCallExpression(parent, { callee: id })) {
    // super(); -> ClassName.prototype.MethodName.call(this);
    parent.arguments.unshift(t.thisExpression());

    if (methodName.name === "constructor") {
      // constructor() { super(); }
      return t.memberExpression(superName, t.identifier("call"));
    } else {
      id = superName;

      // foo() { super(); }
      if (!methodNode.static) {
        id = t.memberExpression(id, t.identifier("prototype"));
      }

      id = t.memberExpression(id, methodName, methodNode.computed);
      return t.memberExpression(id, t.identifier("call"));
    }
  } else if (t.isMemberExpression(parent) && !methodNode.static) {
    // super.test -> ClassName.prototype.test
    return t.memberExpression(superName, t.identifier("prototype"));
  } else {
    return superName;
  }
};

/**
 * Replace all `super` references with a reference to our `superClass`.
 *
 * @param {Node} methodNode MethodDefinition
 */

Class.prototype.replaceInstanceSuperReferences = function (methodNode) {
  var method = methodNode.value;
  var self = this;

  traverse(method, function (node, parent) {
    if (t.isIdentifier(node, { name: "super" })) {
      return self.superIdentifier(methodNode, node, parent);
    } else if (t.isCallExpression(node)) {
      var callee = node.callee;
      if (!t.isMemberExpression(callee)) return;
      if (callee.object.name !== "super") return;

      // super.test(); -> ClassName.prototype.MethodName.call(this);
      callee.property = t.memberExpression(callee.property, t.identifier("call"));
      node.arguments.unshift(t.thisExpression());
    }
  });
};

/**
 * Replace the constructor body of our class.
 *
 * @param {Node} method MethodDefinition
 */

Class.prototype.pushConstructor = function (method) {
  if (method.kind !== "") {
    throw this.file.errorWithNode(method, "illegal kind for constructor method");
  }

  var construct = this.constructor;
  var fn        = method.value;

  this.hasConstructor = true;

  t.inherits(construct, fn);
  t.inheritsComments(construct, method);

  construct.defaults = fn.defaults;
  construct.params   = fn.params;
  construct.body     = fn.body;
  construct.rest     = fn.rest;
};
<|endoftext|>