<commit_before>// activity-test.js
//
// Test the AS2 conversion module
//
// Copyright 2017 AJ Jordan <alex@strugee.net>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

"use strict";

var assert = require("assert"),
    vows = require("vows"),
    uuid = require("uuid");

var suite = vows.describe("AS2 conversion module interface");

var testVocabConversion = function(verb, newVerb) {
    return {
        topic: function(toAS2) {
            var act = {
                id: "urn:uuid:" + uuid.v4(),
                actor: "acct:evan@w3.example",
                verb: verb,
                to: [{objectType: "collection",
                      id: "http://w3.example/socialwg",
                      links: {
                          "next": "http://w3.example/a/next/url",
                          "prev": "http://w3.example/a/prev/url",
                          "last": "http://w3.example/a/last/url"
                      }}],
                target: {
                    id: "urn:uuid:" + uuid.v4(),
                    objectType: "note"
                }
            };

            toAS2(act, this.callback);
        },
        "type uses AS2 vocabulary": function(act) {
            assert.isFalse(act.hasOwnProperty("verb"));
            assert.equal(act["type"], newVerb);
        }
    };
};

suite.addBatch({
    "When we get the AS2 conversion module": {
        topic: function() {
            return require("../lib/as2");
        },
        "it works": function(err, as2) {
            assert.ifError(err);
            assert.isFunction(as2);
        },
        "and we try to convert a post of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                    actor: "acct:tim@w3.example",
                    verb: "post",
                    title: "I am a title!",
                    upstreamDuplicates: [],
                    downstreamDuplicates: [],
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    displayName: "A note"
                };

                as2(act, this.callback);
            },
            "it has an @context": function(act) {
                assert.isTrue(act.hasOwnProperty("@context"));
                assert.equal(act["@context"], "https://www.w3.org/ns/activitystreams");
            },
            "the `post` verb is converted specially to Create in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Create");
            },
            "the object's objectType is renamed to type": function(act) {
                assert.isFalse(act.object.hasOwnProperty("objectType"));
                assert.equal(act.object["type"], "Note");
            },
            "the `to` field's collection's objectType is renamed to type": function(act) {
                assert.isFalse(act.to[0].hasOwnProperty("objectType"));
                assert.equal(act.to[0]["type"], "Collection");
            },
            // XXX should we test for all objectType renames? E.g. in the `to` field?
            "displayName is renamed to name": function(act) {
                assert.isFalse(act.hasOwnProperty("displayName"));
                assert.equal(act.name, "A note");
            },
            "title is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("title"));
            },
            "upstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("upstreamDuplicates"));
            },
            "downstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("downstreamDuplicates"));
            }
        },
        "and we try to convert a post of a note to a collection to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:3738fceb-9705-4fa8-a0d3-59852770dc4d",
                    actor: "acct:chris@w3.example",
                    verb: "post",
                    title: "A rando note I want to categorize",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    target: {
                        id: "http://w3.example/chris/a-collection",
                        objectType: "collection"
                    }
                };

                as2(act, this.callback);
            },
            "the `post` verb is converted specially to Add in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Add");
            }
        },
        "and we try to convert a like of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:db8b4174-a321-430f-bbbe-e11c65dd48ee",
                    actor: "acct:aj@w3.example",
                    verb: "like",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    // TODO check that this syntax is correct
                    target: {
                        id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                        objectType: "note"
                    }
                };

                as2(act, this.callback);
            },
            "verb is renamed to type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "like");
            },
            "the target's objectType is renamed to type": function(act) {
                assert.isFalse(act.target.hasOwnProperty("objectType"));
                assert.equal(act.target["type"], "Note");
            }
        },
        "and we try to convert a submission of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:9e4a902d-8a3a-495d-b73c-cfa0cf32f310",
                    actor: "acct:amy@w3.example",
                    verb: "submit",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:404c13a3-65ba-43f0-a88d-9a2b07a21a17",
                        objectType: "note"
                    },
                    displayName: "A note"
                };

                as2(act, this.callback);
            },
            "the `submit` verb is converted specially to Create in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Create");
            }
        },
        "and we try to convert a submission of a note to a collection to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:8e765132-414a-4535-9949-c4650f22e493",
                    actor: "acct:tantek@w3.example",
                    verb: "submit",
                    title: "Some other random thing",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:aa6c312c-5294-4875-9f16-cb4d586127cb",
                        objectType: "note"
                    },
                    target: {
                        id: "http://w3.example/tantek/a-collection",
                        objectType: "collection"
                    }
                };

                as2(act, this.callback);
            },
            "the `submit` verb is converted specially to Add in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Add");
            }
        },
        "and we try to convert a `share` activity to AS2": testVocabConversion("share", "Announce"),
        "and we try to convert a `attach` activity to AS2": testVocabConversion("attach", "Add"),
        "and we try to convert a `author` activity to AS2": testVocabConversion("author", "Create"),
        "and we try to convert a `favorite` activity to AS2": testVocabConversion("favorite", "Like"),
        "and we try to convert a `flag-as-inappropriate` activity to AS2": testVocabConversion("flag-as-inappropriate", "Flag"),
        "and we try to convert a `play` activity to AS2": testVocabConversion("play", "View"),
        "and we try to convert a `rsvp-maybe` activity to AS2": testVocabConversion("rsvp-maybe", "TentativeAccept"),
        "and we try to convert a `rsvp-no` activity to AS2": testVocabConversion("rsvp-no", "Reject"),
        "and we try to convert a `rsvp-yes` activity to AS2": testVocabConversion("rsvp-yes", "Accept"),
        "and we try to convert a `watch` activity to AS2": testVocabConversion("watch", "View")
    }
});

suite["export"](module);
<commit_msg>Accept to as a scalar; correct type for like activity<commit_after>// as2-test.js
//
// Test the AS2 conversion module
//
// Copyright 2017 AJ Jordan <alex@strugee.net>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

"use strict";

var assert = require("assert"),
    vows = require("vows"),
    uuid = require("uuid");

var suite = vows.describe("AS2 conversion module interface");

var testVocabConversion = function(verb, newVerb) {
    return {
        topic: function(toAS2) {
            var act = {
                id: "urn:uuid:" + uuid.v4(),
                actor: "acct:evan@w3.example",
                verb: verb,
                to: [{objectType: "collection",
                      id: "http://w3.example/socialwg",
                      links: {
                          "next": "http://w3.example/a/next/url",
                          "prev": "http://w3.example/a/prev/url",
                          "last": "http://w3.example/a/last/url"
                      }}],
                target: {
                    id: "urn:uuid:" + uuid.v4(),
                    objectType: "note"
                }
            };

            toAS2(act, this.callback);
        },
        "type uses AS2 vocabulary": function(act) {
            assert.isFalse(act.hasOwnProperty("verb"));
            assert.equal(act["type"], newVerb);
        }
    };
};

suite.addBatch({
    "When we get the AS2 conversion module": {
        topic: function() {
            return require("../lib/as2");
        },
        "it works": function(err, as2) {
            assert.ifError(err);
            assert.isFunction(as2);
        },
        "and we try to convert a post of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                    actor: "acct:tim@w3.example",
                    verb: "post",
                    title: "I am a title!",
                    upstreamDuplicates: [],
                    downstreamDuplicates: [],
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    displayName: "A note"
                };

                as2(act, this.callback);
            },
            "it has an @context": function(act) {
                assert.isTrue(act.hasOwnProperty("@context"));
                assert.equal(act["@context"], "https://www.w3.org/ns/activitystreams");
            },
            "the `post` verb is converted specially to Create in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Create");
            },
            "the object's objectType is renamed to type": function(act) {
                assert.isFalse(act.object.hasOwnProperty("objectType"));
                assert.equal(act.object["type"], "Note");
            },
            "the `to` field's collection's objectType is renamed to type": function(act) {
                assert.isObject(act.to);
                assert.isFalse(act.to.hasOwnProperty("objectType"));
                assert.equal(act.to.type, "Collection");
            },
            // XXX should we test for all objectType renames? E.g. in the `to` field?
            "displayName is renamed to name": function(act) {
                assert.isFalse(act.hasOwnProperty("displayName"));
                assert.equal(act.name, "A note");
            },
            "title is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("title"));
            },
            "upstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("upstreamDuplicates"));
            },
            "downstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("downstreamDuplicates"));
            }
        },
        "and we try to convert a post of a note to a collection to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:3738fceb-9705-4fa8-a0d3-59852770dc4d",
                    actor: "acct:chris@w3.example",
                    verb: "post",
                    title: "A rando note I want to categorize",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    target: {
                        id: "http://w3.example/chris/a-collection",
                        objectType: "collection"
                    }
                };

                as2(act, this.callback);
            },
            "the `post` verb is converted specially to Add in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Add");
            }
        },
        "and we try to convert a like of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:db8b4174-a321-430f-bbbe-e11c65dd48ee",
                    actor: "acct:aj@w3.example",
                    verb: "like",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    // TODO check that this syntax is correct
                    target: {
                        id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                        objectType: "note"
                    }
                };

                as2(act, this.callback);
            },
            "verb is renamed to type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Like");
            },
            "the target's objectType is renamed to type": function(act) {
                assert.isFalse(act.target.hasOwnProperty("objectType"));
                assert.equal(act.target["type"], "Note");
            }
        },
        "and we try to convert a submission of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:9e4a902d-8a3a-495d-b73c-cfa0cf32f310",
                    actor: "acct:amy@w3.example",
                    verb: "submit",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:404c13a3-65ba-43f0-a88d-9a2b07a21a17",
                        objectType: "note"
                    },
                    displayName: "A note"
                };

                as2(act, this.callback);
            },
            "the `submit` verb is converted specially to Create in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Create");
            }
        },
        "and we try to convert a submission of a note to a collection to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:8e765132-414a-4535-9949-c4650f22e493",
                    actor: "acct:tantek@w3.example",
                    verb: "submit",
                    title: "Some other random thing",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg",
                          links: {
                              "next": "http://w3.example/a/next/url",
                              "prev": "http://w3.example/a/prev/url",
                              "last": "http://w3.example/a/last/url"
                          }}],
                    object: {
                        id: "urn:uuid:aa6c312c-5294-4875-9f16-cb4d586127cb",
                        objectType: "note"
                    },
                    target: {
                        id: "http://w3.example/tantek/a-collection",
                        objectType: "collection"
                    }
                };

                as2(act, this.callback);
            },
            "the `submit` verb is converted specially to Add in type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["type"], "Add");
            }
        },
        "and we try to convert a `share` activity to AS2": testVocabConversion("share", "Announce"),
        "and we try to convert a `attach` activity to AS2": testVocabConversion("attach", "Add"),
        "and we try to convert a `author` activity to AS2": testVocabConversion("author", "Create"),
        "and we try to convert a `favorite` activity to AS2": testVocabConversion("favorite", "Like"),
        "and we try to convert a `flag-as-inappropriate` activity to AS2": testVocabConversion("flag-as-inappropriate", "Flag"),
        "and we try to convert a `play` activity to AS2": testVocabConversion("play", "View"),
        "and we try to convert a `rsvp-maybe` activity to AS2": testVocabConversion("rsvp-maybe", "TentativeAccept"),
        "and we try to convert a `rsvp-no` activity to AS2": testVocabConversion("rsvp-no", "Reject"),
        "and we try to convert a `rsvp-yes` activity to AS2": testVocabConversion("rsvp-yes", "Accept"),
        "and we try to convert a `watch` activity to AS2": testVocabConversion("watch", "View")
    }
});

suite["export"](module);
<|endoftext|>


<commit_before>const handler = require("../handler");
const nock = require("nock");
const util = require("../lib/util");
const zlib = require("zlib");

describe("originRequest", function() {
  beforeEach(function() {
    handler.setPrerenderCloudOption(prerendercloud => null);
    nock.cleanAll();
    nock.disableNetConnect();
  });

  function runHandlerWithOriginRequestEvent() {
    beforeEach(function(done) {
      this.cb = jasmine.createSpy("originalCallback").and.callFake(done);
      this.handler(this.event, this.context, this.cb);
    });
  }

  function itReturnsPrerenderCloudResponse(extraHeaders = {}) {
    it("calls callback with prerendered body and headers", function() {
      expect(this.cb).toHaveBeenCalledWith(null, {
        status: "200",
        statusDescription: "OK",
        headers: Object.assign(
          {},
          {
            "content-type": [{ key: "content-type", value: "text/html" }]
          },
          extraHeaders
        ),
        body: "prerendered-body"
      });
    });
  }

  function itReturnsBase64edGzipResponse() {
    it("calls callback with prerendered body and headers", function() {
      expect(this.cb).toHaveBeenCalledWith(null, {
        status: "200",
        statusDescription: "OK",
        headers: Object.assign(
          {},
          {
            "content-type": [{ key: "content-type", value: "text/html" }],
            "content-encoding": [{ key: "content-encoding", value: "gzip" }]
          }
        ),
        body: zlib.gzipSync("prerendered-body").toString("base64"),
        bodyEncoding: "base64"
      });
    });
  }

  function itForwardsRequestToPrerenderCloud(userAgent, uri) {
    it("sends exact URL to prerender server with leading slash", function() {
      expect(this.requestedPrerenderUri).toEqual(uri);
    });

    it("sends prerendercloud middleware user-agent, and curl x-original-user-agent, and gzip", function() {
      expect(this.headersSentToServer).toEqual({
        "user-agent": "prerender-cloud-nodejs-middleware",
        "accept-encoding": "gzip",
        "x-original-user-agent": userAgent,
        host: "service.prerender.cloud"
      });
    });
  }

  function itReturnsOriginalCloudFrontRequestWithNormalPath(uri) {
    it("returns original CloudFront request with normal path", function() {
      expect(this.cb).toHaveBeenCalledWith(null, {
        headers: {
          host: [{ value: "d123.cf.net", key: "Host" }],
          "user-agent": [{ value: "CloudFront", key: "User-Agent" }]
        },
        clientIp: "2001:cdba::3257:9652",
        uri: uri,
        method: "GET"
      });
    });
  }

  beforeEach(function() {
    const self = this;
    this.prerenderServer = nock("https://service.prerender.cloud")
      .get(/.*/)
      .delay(10)
      .reply(function(uri) {
        self.requestedPrerenderUri = uri;
        self.headersSentToServer = this.req.headers;
        return [
          200,
          self.prerenderedContent || "prerendered-body",
          { "content-type": "text/html" }
        ];
      });
    this.handler = handler.originRequest;
    this.event = {
      Records: [
        {
          cf: {
            request: {
              headers: {
                host: [
                  {
                    value: "d123.cf.net",
                    key: "Host"
                  }
                ],
                "user-agent": [
                  {
                    value: "CloudFront",
                    key: "User-Agent"
                  }
                ]
              },
              clientIp: "2001:cdba::3257:9652",
              uri: "",
              method: "GET"
            }
          }
        }
      ]
    };
    this.context = {};
  });

  function withInputs(userAgent, uri, shouldPrerender) {
    beforeEach(function() {
      this.event.Records[0].cf.request.uri = util.createUri(
        uri,
        shouldPrerender
      );
      this.event.Records[0].cf.request.headers[
        "prerendercloud-lambda-edge-original-user-agent"
      ] = [
        {
          value: userAgent,
          key: "prerendercloud-lambda-edge-original-user-agent"
        }
      ];
    });
  }

  function withGzip() {
    beforeEach(function() {
      this.event.Records[0].cf.request.headers["accept-encoding"] = [
        { key: "accept-encoding", value: "gzip" }
      ];
    });
  }

  function withPrerenderedContent(content) {
    beforeEach(function() {
      this.prerenderedContent = content;
    });
  }

  describe("when shouldPrerender is true", function() {
    withInputs("whatever", "/index.html", true);

    describe("when accept-encoding supports gzip", function() {
      withGzip();
      runHandlerWithOriginRequestEvent();

      itReturnsBase64edGzipResponse();
    });

    describe("without protocol", function() {
      runHandlerWithOriginRequestEvent();

      // defaults to https
      itForwardsRequestToPrerenderCloud(
        "whatever",
        "/https://d123.cf.net/index.html"
      );
      itReturnsPrerenderCloudResponse();
    });

    describe("when protocol is forced to http", function() {
      beforeEach(function() {
        handler.setPrerenderCloudOption(prerendercloud =>
          prerendercloud.set("protocol", "http")
        );
      });
      runHandlerWithOriginRequestEvent();

      itForwardsRequestToPrerenderCloud(
        "whatever",
        "/http://d123.cf.net/index.html"
      );
      itReturnsPrerenderCloudResponse();
    });
  });

  describe("when shouldPrerender is false", function() {
    withInputs("whatever", "/index.html", false);
    runHandlerWithOriginRequestEvent();

    itReturnsOriginalCloudFrontRequestWithNormalPath("/index.html");
  });

  // on a timeout, it will call "next" which flows through to origin
  // instead of prerendercloud, so we need to do the path rewrite to index.html
  describe("when timeout", function() {
    beforeEach(function() {
      handler.setPrerenderCloudOption(prerendercloud =>
        prerendercloud.set("timeout", 1)
      );
    });

    describe("when an HTML file", function() {
      withInputs("prerendercloud", "/some/path", true);
      runHandlerWithOriginRequestEvent();

      itReturnsOriginalCloudFrontRequestWithNormalPath("/index.html");
    });

    describe("when a file with an extension", function() {
      withInputs("prerendercloud", "/app.js", true);
      runHandlerWithOriginRequestEvent();

      itReturnsOriginalCloudFrontRequestWithNormalPath("/app.js");
    });
  });

  // lambda has a 256kb max response
  describe("when shouldPrerender is true but size is over 256kb", function() {
    withInputs("whatever", "/index.html", true);
    withPrerenderedContent(new Buffer(256000));

    runHandlerWithOriginRequestEvent();

    itForwardsRequestToPrerenderCloud(
      "whatever",
      "/https://d123.cf.net/index.html"
    );

    itReturnsOriginalCloudFrontRequestWithNormalPath("/index.html");
  });
});
<commit_msg>Fix spec for #10 (256kb to 1mb limit)<commit_after>const handler = require("../handler");
const nock = require("nock");
const util = require("../lib/util");
const zlib = require("zlib");

describe("originRequest", function() {
  beforeEach(function() {
    handler.setPrerenderCloudOption(prerendercloud => null);
    nock.cleanAll();
    nock.disableNetConnect();
  });

  function runHandlerWithOriginRequestEvent() {
    beforeEach(function(done) {
      this.cb = jasmine.createSpy("originalCallback").and.callFake(done);
      this.handler(this.event, this.context, this.cb);
    });
  }

  function itReturnsPrerenderCloudResponse(extraHeaders = {}) {
    it("calls callback with prerendered body and headers", function() {
      expect(this.cb).toHaveBeenCalledWith(null, {
        status: "200",
        statusDescription: "OK",
        headers: Object.assign(
          {},
          {
            "content-type": [{ key: "content-type", value: "text/html" }]
          },
          extraHeaders
        ),
        body: "prerendered-body"
      });
    });
  }

  function itReturnsBase64edGzipResponse() {
    it("calls callback with prerendered body and headers", function() {
      expect(this.cb).toHaveBeenCalledWith(null, {
        status: "200",
        statusDescription: "OK",
        headers: Object.assign(
          {},
          {
            "content-type": [{ key: "content-type", value: "text/html" }],
            "content-encoding": [{ key: "content-encoding", value: "gzip" }]
          }
        ),
        body: zlib.gzipSync("prerendered-body").toString("base64"),
        bodyEncoding: "base64"
      });
    });
  }

  function itForwardsRequestToPrerenderCloud(userAgent, uri) {
    it("sends exact URL to prerender server with leading slash", function() {
      expect(this.requestedPrerenderUri).toEqual(uri);
    });

    it("sends prerendercloud middleware user-agent, and curl x-original-user-agent, and gzip", function() {
      expect(this.headersSentToServer).toEqual({
        "user-agent": "prerender-cloud-nodejs-middleware",
        "accept-encoding": "gzip",
        "x-original-user-agent": userAgent,
        host: "service.prerender.cloud"
      });
    });
  }

  function itReturnsOriginalCloudFrontRequestWithNormalPath(uri) {
    it("returns original CloudFront request with normal path", function() {
      expect(this.cb).toHaveBeenCalledWith(null, {
        headers: {
          host: [{ value: "d123.cf.net", key: "Host" }],
          "user-agent": [{ value: "CloudFront", key: "User-Agent" }]
        },
        clientIp: "2001:cdba::3257:9652",
        uri: uri,
        method: "GET"
      });
    });
  }

  beforeEach(function() {
    const self = this;
    this.prerenderServer = nock("https://service.prerender.cloud")
      .get(/.*/)
      .delay(10)
      .reply(function(uri) {
        self.requestedPrerenderUri = uri;
        self.headersSentToServer = this.req.headers;
        return [
          200,
          self.prerenderedContent || "prerendered-body",
          { "content-type": "text/html" }
        ];
      });
    this.handler = handler.originRequest;
    this.event = {
      Records: [
        {
          cf: {
            request: {
              headers: {
                host: [
                  {
                    value: "d123.cf.net",
                    key: "Host"
                  }
                ],
                "user-agent": [
                  {
                    value: "CloudFront",
                    key: "User-Agent"
                  }
                ]
              },
              clientIp: "2001:cdba::3257:9652",
              uri: "",
              method: "GET"
            }
          }
        }
      ]
    };
    this.context = {};
  });

  function withInputs(userAgent, uri, shouldPrerender) {
    beforeEach(function() {
      this.event.Records[0].cf.request.uri = util.createUri(
        uri,
        shouldPrerender
      );
      this.event.Records[0].cf.request.headers[
        "prerendercloud-lambda-edge-original-user-agent"
      ] = [
        {
          value: userAgent,
          key: "prerendercloud-lambda-edge-original-user-agent"
        }
      ];
    });
  }

  function withGzip() {
    beforeEach(function() {
      this.event.Records[0].cf.request.headers["accept-encoding"] = [
        { key: "accept-encoding", value: "gzip" }
      ];
    });
  }

  function withPrerenderedContent(content) {
    beforeEach(function() {
      this.prerenderedContent = content;
    });
  }

  describe("when shouldPrerender is true", function() {
    withInputs("whatever", "/index.html", true);

    describe("when accept-encoding supports gzip", function() {
      withGzip();
      runHandlerWithOriginRequestEvent();

      itReturnsBase64edGzipResponse();
    });

    describe("without protocol", function() {
      runHandlerWithOriginRequestEvent();

      // defaults to https
      itForwardsRequestToPrerenderCloud(
        "whatever",
        "/https://d123.cf.net/index.html"
      );
      itReturnsPrerenderCloudResponse();
    });

    describe("when protocol is forced to http", function() {
      beforeEach(function() {
        handler.setPrerenderCloudOption(prerendercloud =>
          prerendercloud.set("protocol", "http")
        );
      });
      runHandlerWithOriginRequestEvent();

      itForwardsRequestToPrerenderCloud(
        "whatever",
        "/http://d123.cf.net/index.html"
      );
      itReturnsPrerenderCloudResponse();
    });
  });

  describe("when shouldPrerender is false", function() {
    withInputs("whatever", "/index.html", false);
    runHandlerWithOriginRequestEvent();

    itReturnsOriginalCloudFrontRequestWithNormalPath("/index.html");
  });

  // on a timeout, it will call "next" which flows through to origin
  // instead of prerendercloud, so we need to do the path rewrite to index.html
  describe("when timeout", function() {
    beforeEach(function() {
      handler.setPrerenderCloudOption(prerendercloud =>
        prerendercloud.set("timeout", 1)
      );
    });

    describe("when an HTML file", function() {
      withInputs("prerendercloud", "/some/path", true);
      runHandlerWithOriginRequestEvent();

      itReturnsOriginalCloudFrontRequestWithNormalPath("/index.html");
    });

    describe("when a file with an extension", function() {
      withInputs("prerendercloud", "/app.js", true);
      runHandlerWithOriginRequestEvent();

      itReturnsOriginalCloudFrontRequestWithNormalPath("/app.js");
    });
  });

  // lambda has a 1mb max response
  describe("when shouldPrerender is true but size is over 1mb", function() {
    withInputs("whatever", "/index.html", true);
    withPrerenderedContent(new Buffer(1048001));

    runHandlerWithOriginRequestEvent();

    itForwardsRequestToPrerenderCloud(
      "whatever",
      "/https://d123.cf.net/index.html"
    );

    itReturnsOriginalCloudFrontRequestWithNormalPath("/index.html");
  });
});
<|endoftext|>


<commit_before>(function() {
  'use strict';

  function jsonldVis(jsonld, selector, config) {
    if (!arguments.length) return jsonldVis;
    config = config || {};

    var h = config.h || 600
      , w = config.w || 800
      , maxLabelWidth = config.maxLabelWidth || 250
      , transitionDuration = config.transitionDuration || 750
      , transitionEase = config.transitionEase || 'cubic-in-out'
      , radius = config.radius || 5;

    var i = 0;

    var tree = d3.layout.tree()
      .size([h, w]);

    var diagonal = d3.svg.diagonal()
      .projection(function(d) { return [d.y, d.x]; });

    var svg = d3.select(selector).append('svg')
      .attr('width', w)
      .attr('height', h)
      .append('g')
      .attr('transform', 'translate(' + maxLabelWidth + ',0)');

    var tip = d3.tip()
      .direction(function(d) {
        return d.children || d._children ? 'w' : 'e';
      })
      .offset(function(d) {
        return d.children || d._children ? [0, -3] : [0, 3];
      })
      .attr('class', 'd3-tip')
      .html(function(d) {
        return '' + d.valueExtended + '';
      });

    svg.call(tip);

    var root = jsonldTree(jsonld);
    root.x0 = h / 2;
    root.y0 = 0;
    root.children.forEach(collapse);

    function changeSVGWidth(newWidth) {
      if (w !== newWidth) {
        d3.select(selector + ' > svg').style('width', newWidth);
      }
    }

    function jsonldTree(source) {
      var tree = {};

      if ('@id' in source) {
        tree.isIdNode = true;
        tree.name = source['@id'];
      } else {
        tree.isIdNode = true;
        // random id, can replace with actual uuid generator if needed
        tree.name = '_' + Math.random().toString(10).slice(-7);
      }

      var children = [];
      Object.keys(source).forEach(function(key) {
        if (key === '@id' || key === '@context' || source[key] === null) return;

        var valueExtended, value;
        if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
          children.push({
            name: key,
            children: [jsonldTree(source[key])]
          });
        } else if (Array.isArray(source[key])) {
          children.push({
            name: key,
            children: source[key].map(function(item) {
              if (typeof item === 'object') {
                return jsonldTree(item);
              } else {
                return { name: item };
              }
            })
          });
        } else {
          valueExtended = source[key];
          value = valueExtended;
          if (value.length > maxLabelWidth / 10) {
            value = value.slice(0, Math.floor(maxLabelWidth / 10)) + '...';
            children.push({
              name: key,
              value: value,
              valueExtended: valueExtended
            });
          } else {
            children.push({
              name: key,
              value: value
            });
          }
        }
      });

      if (children.length) {
        tree.children = children;
      }

      return tree;
    }

    function update(source) {
      var nodes = tree.nodes(root).reverse();
      var links = tree.links(nodes);

      nodes.forEach(function(d) { d.y = d.depth * maxLabelWidth; });

      var node = svg.selectAll('g.node')
        .data(nodes, function(d) { return d.id || (d.id = ++i); });

      var nodeEnter = node.enter()
        .append('g')
        .attr('class', 'node')
        .attr('transform', function(d) { return 'translate(' + source.y0 + ',' + source.x0 + ')'; })
        .on('click', click);

      nodeEnter.append('circle')
        .attr('r', 0)
        .style('stroke-width', function(d) {
          return d.isIdNode ? '2px' : '1px';
        })
        .style('stroke', function(d) {
          return d.isIdNode ? '#F7CA18' : '#4ECDC4';
        })
        .style('fill', function(d) {
          if (d.isIdNode) {
            return d._children ? '#F5D76E' : 'white';
          } else {
            return d._children ? '#86E2D5' : 'white';
          }
        })
        .on('mouseover', function(d) { if (d.valueExtended) tip.show(d); })
        .on('mouseout', tip.hide);

      nodeEnter.append('text')
        .attr('x', function(d) {
          var spacing = computeRadius(d) + 5;
          return d.children || d._children ? -spacing : spacing;
        })
        .attr('dy', '4')
        .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })
        .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); })
        .style('fill-opacity', 0);

      var maxSpan = Math.max.apply(Math, nodes.map(function(d) { return d.y + maxLabelWidth; }));
      if (maxSpan + maxLabelWidth + 20 > w) {
        changeSVGWidth(maxSpan + maxLabelWidth);
        d3.select(selector).node().scrollLeft = source.y0;
      }

      var nodeUpdate = node.transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });

      nodeUpdate.select('circle')
        .attr('r', function(d) { return computeRadius(d); })
        .style('stroke-width', function(d) {
          return d.isIdNode ? '2px' : '1px';
        })
        .style('stroke', function(d) {
          return d.isIdNode ? '#F7CA18' : '#4ECDC4';
        })
        .style('fill', function(d) {
          if (d.isIdNode) {
            return d._children ? '#F5D76E' : 'white';
          } else {
            return d._children ? '#86E2D5' : 'white';
          }
        });

      nodeUpdate.select('text').style('fill-opacity', 1);

      var nodeExit = node.exit().transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })
        .remove();

      nodeExit.select('circle').attr('r', 0);
      nodeExit.select('text').style('fill-opacity', 0);

      var link = svg.selectAll('path.link')
        .data(links, function(d) { return d.target.id; });

      link.enter().insert('path', 'g')
        .attr('class', 'link')
        .attr('d', function(d) {
          var o = { x: source.x0, y: source.y0 };
          return diagonal({ source: o, target: o });
        });

      link.transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('d', diagonal);

      link.exit().transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('d', function(d) {
          var o = { x: source.x, y: source.y };
          return diagonal({ source: o, target: o });
        })
        .remove();

      nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    }

    function computeRadius(d) {
      if (d.children || d._children) {
        return radius + (radius * numEndNodes(d) / 10);
      } else {
        return radius;
      }
    }

    function numEndNodes(n) {
      var num = 0;
      if (n.children) {
        n.children.forEach(function(c) {
          num += numEndNodes(c);
        });
      } else if (n._children) {
        n._children.forEach(function(c) {
          num += numEndNodes(c);
        });
      } else {
        num++;
      }
      return num;
    }

    function click(d) {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
        d._children = null;
      }
      update(d);
    }

    function collapse(d) {
      if (d.children) {
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = null;
      }
    }

    update(root);
  }

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = jsonldVis;
  } else {
    d3.jsonldVis = jsonldVis;
  }
})();
<commit_msg>auto-expand blank nodes<commit_after>(function() {
  'use strict';

  function jsonldVis(jsonld, selector, config) {
    if (!arguments.length) return jsonldVis;
    config = config || {};

    var h = config.h || 600
      , w = config.w || 800
      , maxLabelWidth = config.maxLabelWidth || 250
      , transitionDuration = config.transitionDuration || 750
      , transitionEase = config.transitionEase || 'cubic-in-out'
      , radius = config.radius || 5;

    var i = 0;

    var tree = d3.layout.tree()
      .size([h, w]);

    var diagonal = d3.svg.diagonal()
      .projection(function(d) { return [d.y, d.x]; });

    var svg = d3.select(selector).append('svg')
      .attr('width', w)
      .attr('height', h)
      .append('g')
      .attr('transform', 'translate(' + maxLabelWidth + ',0)');

    var tip = d3.tip()
      .direction(function(d) {
        return d.children || d._children ? 'w' : 'e';
      })
      .offset(function(d) {
        return d.children || d._children ? [0, -3] : [0, 3];
      })
      .attr('class', 'd3-tip')
      .html(function(d) {
        return '' + d.valueExtended + '';
      });

    svg.call(tip);

    var root = jsonldTree(jsonld);
    root.x0 = h / 2;
    root.y0 = 0;
    root.children.forEach(collapse);

    function changeSVGWidth(newWidth) {
      if (w !== newWidth) {
        d3.select(selector + ' > svg').style('width', newWidth);
      }
    }

    function jsonldTree(source) {
      var tree = {};

      if ('@id' in source) {
        tree.isIdNode = true;
        tree.name = source['@id'];
      } else {
        tree.isIdNode = true;
        tree.isBlankNode = true;
        // random id, can replace with actual uuid generator if needed
        tree.name = '_' + Math.random().toString(10).slice(-7);
      }

      var children = [];
      Object.keys(source).forEach(function(key) {
        if (key === '@id' || key === '@context' || source[key] === null) return;

        var valueExtended, value;
        if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
          children.push({
            name: key,
            children: [jsonldTree(source[key])]
          });
        } else if (Array.isArray(source[key])) {
          children.push({
            name: key,
            children: source[key].map(function(item) {
              if (typeof item === 'object') {
                return jsonldTree(item);
              } else {
                return { name: item };
              }
            })
          });
        } else {
          valueExtended = source[key];
          value = valueExtended;
          if (value.length > maxLabelWidth / 10) {
            value = value.slice(0, Math.floor(maxLabelWidth / 10)) + '...';
            children.push({
              name: key,
              value: value,
              valueExtended: valueExtended
            });
          } else {
            children.push({
              name: key,
              value: value
            });
          }
        }
      });

      if (children.length) {
        tree.children = children;
      }

      return tree;
    }

    function update(source) {
      var nodes = tree.nodes(root).reverse();
      var links = tree.links(nodes);

      nodes.forEach(function(d) { d.y = d.depth * maxLabelWidth; });

      var node = svg.selectAll('g.node')
        .data(nodes, function(d) { return d.id || (d.id = ++i); });

      var nodeEnter = node.enter()
        .append('g')
        .attr('class', 'node')
        .attr('transform', function(d) { return 'translate(' + source.y0 + ',' + source.x0 + ')'; })
        .on('click', click);

      nodeEnter.append('circle')
        .attr('r', 0)
        .style('stroke-width', function(d) {
          return d.isIdNode ? '2px' : '1px';
        })
        .style('stroke', function(d) {
          return d.isIdNode ? '#F7CA18' : '#4ECDC4';
        })
        .style('fill', function(d) {
          if (d.isIdNode) {
            return d._children ? '#F5D76E' : 'white';
          } else {
            return d._children ? '#86E2D5' : 'white';
          }
        })
        .on('mouseover', function(d) { if (d.valueExtended) tip.show(d); })
        .on('mouseout', tip.hide);

      nodeEnter.append('text')
        .attr('x', function(d) {
          var spacing = computeRadius(d) + 5;
          return d.children || d._children ? -spacing : spacing;
        })
        .attr('dy', '4')
        .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })
        .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); })
        .style('fill-opacity', 0);

      var maxSpan = Math.max.apply(Math, nodes.map(function(d) { return d.y + maxLabelWidth; }));
      if (maxSpan + maxLabelWidth + 20 > w) {
        changeSVGWidth(maxSpan + maxLabelWidth);
        d3.select(selector).node().scrollLeft = source.y0;
      }

      var nodeUpdate = node.transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });

      nodeUpdate.select('circle')
        .attr('r', function(d) { return computeRadius(d); })
        .style('stroke-width', function(d) {
          return d.isIdNode ? '2px' : '1px';
        })
        .style('stroke', function(d) {
          return d.isIdNode ? '#F7CA18' : '#4ECDC4';
        })
        .style('fill', function(d) {
          if (d.isIdNode) {
            return d._children ? '#F5D76E' : 'white';
          } else {
            return d._children ? '#86E2D5' : 'white';
          }
        });

      nodeUpdate.select('text').style('fill-opacity', 1);

      var nodeExit = node.exit().transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })
        .remove();

      nodeExit.select('circle').attr('r', 0);
      nodeExit.select('text').style('fill-opacity', 0);

      var link = svg.selectAll('path.link')
        .data(links, function(d) { return d.target.id; });

      link.enter().insert('path', 'g')
        .attr('class', 'link')
        .attr('d', function(d) {
          var o = { x: source.x0, y: source.y0 };
          return diagonal({ source: o, target: o });
        });

      link.transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('d', diagonal);

      link.exit().transition()
        .duration(transitionDuration)
        .ease(transitionEase)
        .attr('d', function(d) {
          var o = { x: source.x, y: source.y };
          return diagonal({ source: o, target: o });
        })
        .remove();

      nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    }

    function computeRadius(d) {
      if (d.children || d._children) {
        return radius + (radius * numEndNodes(d) / 10);
      } else {
        return radius;
      }
    }

    function numEndNodes(n) {
      var num = 0;
      if (n.children) {
        n.children.forEach(function(c) {
          num += numEndNodes(c);
        });
      } else if (n._children) {
        n._children.forEach(function(c) {
          num += numEndNodes(c);
        });
      } else {
        num++;
      }
      return num;
    }

    function click(d) {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
        d._children = null;
      }

      update(d);

      // fast-forward blank nodes
      if (d.children) {
        d.children.forEach(function(child) {
          if (child.isBlankNode && child._children) {
            click(child);
          }
        });
      }
    }

    function collapse(d) {
      if (d.children) {
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = null;
      }
    }

    update(root);
  }

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = jsonldVis;
  } else {
    d3.jsonldVis = jsonldVis;
  }
})();
<|endoftext|>


<commit_before>
var expect = require('chai').expect;
var BST = require('../');

describe('A Binary Search Tree', function () {

    var bst;

    beforeEach(function () {
        bst = new BST();
    });

    afterEach(function () {
        bst = null;
    });

    describe('basic setup', function () {

        it('has a working test framework', function () {
            expect(true).to.be.true;
        });

        it('correctly instantiates an instance', function () {
            expect(bst).to.exist;
        });

        it('sets a root node when the first item is added', function () {
            expect(bst.root).to.not.exist;
            bst.add(50);
            expect(bst.root).to.exist;
        });
    });

    describe('basic functionality', function () {

        beforeEach(function () {
            bst.add(50);
            bst.add(75);
            bst.add(25);
            bst.add(90);
            bst.add(80);
            bst.add(15);
            bst.add(10);
            bst.add(85);
        });

        it('contains a key after it is added to the tree', function () {
            expect(bst.contains(75)).to.be.true;
            expect(bst.contains(87)).to.be.false;
        });

        it('returns the min value in the tree', function () {
            expect(bst.min()).to.equal(10);
        });

        it('returns the max value in the tree', function () {
            expect(bst.max()).to.equal(90);
        });

        it('returns false if it attempts to remove a value not in the tree', function () {
            expect(bst.contains(32)).to.be.false;
            expect(bst.remove(32)).to.be.false;
        });

        it('returns true if it removes a value from the tree', function () {
            expect(bst.contains(90)).to.be.true;
            expect(bst.remove(90)).to.be.true;
            expect(bst.contains(90)).to.be.false;

        });
    });

    describe('tree traversal', function () {

        var bstArray;

        beforeEach(function () {
            bst.add(50);
            bst.add(75);
            bst.add(25);
            bst.add(90);
            bst.add(80);
            bst.add(15);
            bst.add(10);
            bst.add(85);

            bstArray = [];
        });

        it('correctly traverses the tree in order', function () {
            expect(bstArray).to.be.empty;

            bst.inOrderTraversal(function (key) {
                bstArray.push(key);
            });

            expect(bstArray).to.have.length(8);
            expect(bstArray[0]).to.equal(10);
            expect(bstArray[bstArray.length - 1]).to.equal(90);
        });

        it('correctly traverses the tree pre order');
        it('correctly traverses the tree post order');
    });
});
<commit_msg>Add preOrder traversal test<commit_after>
var expect = require('chai').expect;
var BST = require('../');

describe('A Binary Search Tree', function () {

    var bst;

    beforeEach(function () {
        bst = new BST();
    });

    afterEach(function () {
        bst = null;
    });

    describe('basic setup', function () {

        it('has a working test framework', function () {
            expect(true).to.be.true;
        });

        it('correctly instantiates an instance', function () {
            expect(bst).to.exist;
        });

        it('sets a root node when the first item is added', function () {
            expect(bst.root).to.not.exist;
            bst.add(50);
            expect(bst.root).to.exist;
        });
    });

    describe('basic functionality', function () {

        beforeEach(function () {
            bst.add(50);
            bst.add(75);
            bst.add(25);
            bst.add(90);
            bst.add(80);
            bst.add(15);
            bst.add(10);
            bst.add(85);
        });

        it('contains a key after it is added to the tree', function () {
            expect(bst.contains(75)).to.be.true;
            expect(bst.contains(87)).to.be.false;
        });

        it('returns the min value in the tree', function () {
            expect(bst.min()).to.equal(10);
        });

        it('returns the max value in the tree', function () {
            expect(bst.max()).to.equal(90);
        });

        it('returns false if it attempts to remove a value not in the tree', function () {
            expect(bst.contains(32)).to.be.false;
            expect(bst.remove(32)).to.be.false;
        });

        it('returns true if it removes a value from the tree', function () {
            expect(bst.contains(90)).to.be.true;
            expect(bst.remove(90)).to.be.true;
            expect(bst.contains(90)).to.be.false;

        });
    });

    describe('tree traversal', function () {

        var bstArray;

        beforeEach(function () {
            bst.add(50);
            bst.add(75);
            bst.add(25);
            bst.add(90);
            bst.add(80);
            bst.add(15);
            bst.add(10);
            bst.add(85);
            bst.add(30);
            bst.add(100);

            bstArray = [];
        });

        it('correctly traverses the tree in order', function () {
            expect(bstArray).to.be.empty;

            bst.inOrderTraversal(function (key) {
                bstArray.push(key);
            });

            expect(bstArray).to.have.length(10);
            expect(bstArray[0]).to.equal(10);
            expect(bstArray[bstArray.length - 1]).to.equal(100);
        });

        it('correctly traverses the tree pre order', function () {
            expect(bstArray).to.be.empty;

            bst.preOrderTraversal(function (key) {
                bstArray.push(key);
            });

            expect(bstArray).to.have.length(10);
            expect(bstArray[0]).to.equal(50);
            expect(bstArray[bstArray.length - 1]).to.equal(100);
        });

        it('correctly traverses the tree post order');
    });
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';

import ButtonShuffle from './ButtonShuffle.react';
import ButtonRepeat from './ButtonRepeat.react';
import { ProgressBar } from 'react-bootstrap';

import AppActions from '../../actions/AppActions';
import Player from '../../lib/player';
import utils from '../../utils/utils';

class PlayingBarInfos extends React.Component {
  static propTypes = {
    trackPlaying: PropTypes.object,
    shuffle: PropTypes.bool,
    repeat: PropTypes.string,
  }

  constructor(props) {
    super(props);

    this.state = {
      elapsed: 0,
      showTooltip: false,
      duration: null,
      x: null,
      dragging: false,
    };

    this.tick = this.tick.bind(this);

    this.dragOver = this.dragOver.bind(this);
    this.dragEnd = this.dragEnd.bind(this);

    this.jumpAudioTo = this.jumpAudioTo.bind(this);
    this.showTooltip = this.showTooltip.bind(this);
    this.hideTooltip = this.hideTooltip.bind(this);
  }

  componentDidMount() {
    this.timer = setInterval(this.tick, 250);

    window.addEventListener('mousemove', this.dragOver);
    window.addEventListener('mouseup', this.dragEnd);
  }

  componentWillUnmount() {
    clearInterval(this.timer);

    window.removeEventListener('mousemove', this.dragOver);
    window.removeEventListener('mouseup', this.dragEnd);
  }

  tick() {
    this.setState({ elapsed: Player.getCurrentTime() });
  }

  jumpAudioTo(e) {
    this.setState({ dragging: true });

    const { trackPlaying } = this.props;

    const bar = document.querySelector('.now-playing-bar');
    const percent = ((e.pageX - (bar.offsetLeft + bar.offsetParent.offsetLeft)) / bar.offsetWidth) * 100;

    const jumpTo = (percent * trackPlaying.duration) / 100;

    AppActions.player.jumpTo(jumpTo);
  }

  dragOver(e) {
    // Chack if it's needed to update currentTime
    if (this.state.dragging) {
      const { trackPlaying } = this.props;

      const playingBar = this.refs.playingBar;
      const playingBarRect = playingBar.getBoundingClientRect();

      const barWidth = playingBar.offsetWidth;
      const offsetX = Math.min(Math.max(0, e.pageX - playingBarRect.left), barWidth);

      const percent = offsetX / barWidth * 100;

      const jumpTo = (percent * trackPlaying.duration) / 100;

      AppActions.player.jumpTo(jumpTo);
    }
  }

  dragEnd() {
    if (this.state.dragging) {
      this.setState({ dragging: false });
    }
  }

  showTooltip(e) {
    const { trackPlaying } = this.props;

    const offsetX = e.nativeEvent.offsetX;
    const barWidth = e.currentTarget.offsetWidth;

    const percent = offsetX / barWidth * 100;

    const time = (percent * trackPlaying.duration) / 100;

    this.setState({
      duration: time,
      x: percent,
    });
  }

  hideTooltip() {
    this.setState({
      duration: null,
      x: null,
    });
  }

  render() {
    const { trackPlaying } = this.props;
    let elapsedPercent;

    if (this.state.elapsed < trackPlaying.duration) elapsedPercent = this.state.elapsed * 100 / trackPlaying.duration;

    const nowPlayingTooltipClasses = classnames('playing-bar-tooltip', {
      hidden: this.state.duration === null,
    });

    return (

            <ButtonRepeat repeat={this.props.repeat} />
            <ButtonShuffle shuffle={this.props.shuffle} />

              {trackPlaying.title}

             by

              {trackPlaying.artist.join(', ')}

             on

              {trackPlaying.album}

            {utils.parseDuration(this.state.elapsed)} / {utils.parseDuration(trackPlaying.duration)}

          <div className={nowPlayingTooltipClasses} style={{ left: `${this.state.x}%` }}>
            {utils.parseDuration(this.state.duration)}

          <ProgressBar
            now={elapsedPercent}
            onMouseDown={this.jumpAudioTo}
            onMouseMove={this.showTooltip}
            onMouseLeave={this.hideTooltip}
          />

    );
  }
}

export default PlayingBarInfos;
<commit_msg>Use timeupdate event instead of setInterval<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';

import ButtonShuffle from './ButtonShuffle.react';
import ButtonRepeat from './ButtonRepeat.react';
import { ProgressBar } from 'react-bootstrap';

import AppActions from '../../actions/AppActions';
import Player from '../../lib/player';
import utils from '../../utils/utils';

class PlayingBarInfos extends React.Component {
  static propTypes = {
    trackPlaying: PropTypes.object,
    shuffle: PropTypes.bool,
    repeat: PropTypes.string,
  }

  constructor(props) {
    super(props);

    this.state = {
      elapsed: 0,
      showTooltip: false,
      duration: null,
      x: null,
      dragging: false,
    };

    this.tick = this.tick.bind(this);

    this.dragOver = this.dragOver.bind(this);
    this.dragEnd = this.dragEnd.bind(this);

    this.jumpAudioTo = this.jumpAudioTo.bind(this);
    this.showTooltip = this.showTooltip.bind(this);
    this.hideTooltip = this.hideTooltip.bind(this);
  }

  componentDidMount() {
    Player.getAudio().addEventListener('timeupdate', this.tick);

    window.addEventListener('mousemove', this.dragOver);
    window.addEventListener('mouseup', this.dragEnd);
  }

  componentWillUnmount() {
    Player.getAudio().removeEventListener('timeupdate', this.tick);

    window.removeEventListener('mousemove', this.dragOver);
    window.removeEventListener('mouseup', this.dragEnd);
  }

  tick() {
    this.setState({ elapsed: Player.getCurrentTime() });
  }

  jumpAudioTo(e) {
    this.setState({ dragging: true });

    const { trackPlaying } = this.props;

    const bar = document.querySelector('.now-playing-bar');
    const percent = ((e.pageX - (bar.offsetLeft + bar.offsetParent.offsetLeft)) / bar.offsetWidth) * 100;

    const jumpTo = (percent * trackPlaying.duration) / 100;

    AppActions.player.jumpTo(jumpTo);
  }

  dragOver(e) {
    // Chack if it's needed to update currentTime
    if (this.state.dragging) {
      const { trackPlaying } = this.props;

      const playingBar = this.refs.playingBar;
      const playingBarRect = playingBar.getBoundingClientRect();

      const barWidth = playingBar.offsetWidth;
      const offsetX = Math.min(Math.max(0, e.pageX - playingBarRect.left), barWidth);

      const percent = offsetX / barWidth * 100;

      const jumpTo = (percent * trackPlaying.duration) / 100;

      AppActions.player.jumpTo(jumpTo);
    }
  }

  dragEnd() {
    if (this.state.dragging) {
      this.setState({ dragging: false });
    }
  }

  showTooltip(e) {
    const { trackPlaying } = this.props;

    const offsetX = e.nativeEvent.offsetX;
    const barWidth = e.currentTarget.offsetWidth;

    const percent = offsetX / barWidth * 100;

    const time = (percent * trackPlaying.duration) / 100;

    this.setState({
      duration: time,
      x: percent,
    });
  }

  hideTooltip() {
    this.setState({
      duration: null,
      x: null,
    });
  }

  render() {
    const { trackPlaying } = this.props;
    let elapsedPercent;

    if (this.state.elapsed < trackPlaying.duration) elapsedPercent = this.state.elapsed * 100 / trackPlaying.duration;

    const nowPlayingTooltipClasses = classnames('playing-bar-tooltip', {
      hidden: this.state.duration === null,
    });

    return (

            <ButtonRepeat repeat={this.props.repeat} />
            <ButtonShuffle shuffle={this.props.shuffle} />

              {trackPlaying.title}

             by

              {trackPlaying.artist.join(', ')}

             on

              {trackPlaying.album}

            {utils.parseDuration(this.state.elapsed)} / {utils.parseDuration(trackPlaying.duration)}

          <div className={nowPlayingTooltipClasses} style={{ left: `${this.state.x}%` }}>
            {utils.parseDuration(this.state.duration)}

          <ProgressBar
            now={elapsedPercent}
            onMouseDown={this.jumpAudioTo}
            onMouseMove={this.showTooltip}
            onMouseLeave={this.hideTooltip}
          />

    );
  }
}

export default PlayingBarInfos;
<|endoftext|>


<commit_before>
var assert = require('assert')
  , ref = require('ref')
  , ffi = require('../')
  , int = ref.types.int

describe('Callback', function () {

  afterEach(gc)

  it('should create a C function pointer from a JS function', function () {
    var callback = ffi.Callback('void', [ ], function (val) { })
    assert(Buffer.isBuffer(callback))
  })

  it('should be invokable by an ffi\'d ForeignFunction', function () {
    var funcPtr = ffi.Callback(int, [ int ], Math.abs)
    var func = ffi.ForeignFunction(funcPtr, int, [ int ])
    assert.equal(1234, func(-1234))
  })

  describe('async', function () {

    it('should be invokable asynchronously by an ffi\'d ForeignFunction', function () {
      var funcPtr = ffi.Callback(int, [ int ], Math.abs)
      var func = ffi.ForeignFunction(funcPtr, int, [ int ])
      func.async(-9999, function (err, res) {
        assert.equal(null, err)
        assert.equal(9999, res)
      })
    })

  })

})
<commit_msg>test: add a test for a Callback with a `void` return type<commit_after>
var assert = require('assert')
  , ref = require('ref')
  , ffi = require('../')
  , int = ref.types.int

describe('Callback', function () {

  afterEach(gc)

  it('should create a C function pointer from a JS function', function () {
    var callback = ffi.Callback('void', [ ], function (val) { })
    assert(Buffer.isBuffer(callback))
  })

  it('should be invokable by an ffi\'d ForeignFunction', function () {
    var funcPtr = ffi.Callback(int, [ int ], Math.abs)
    var func = ffi.ForeignFunction(funcPtr, int, [ int ])
    assert.equal(1234, func(-1234))
  })

  it('should work with a "void" return type', function () {
    var funcPtr = ffi.Callback('void', [ ], function (val) { })
    var func = ffi.ForeignFunction(funcPtr, 'void', [ ])
    assert.strictEqual(null, func())
  })

  describe('async', function () {

    it('should be invokable asynchronously by an ffi\'d ForeignFunction', function () {
      var funcPtr = ffi.Callback(int, [ int ], Math.abs)
      var func = ffi.ForeignFunction(funcPtr, int, [ int ])
      func.async(-9999, function (err, res) {
        assert.equal(null, err)
        assert.equal(9999, res)
      })
    })

  })

})
<|endoftext|>