<commit_before>var assert = require('chai').assert;
var request = require('supertest');
var uuid = require('node-uuid');

var host_url = 'http://localhost:8080'
var container_url = host_url + '/annotations/';

// just ld+json here as the full profile'd media type is a SHOULD
const MEDIA_TYPE = 'application/ld+json';
const MEDIA_TYPE_REGEX = /application\/ld\+json/;

describe('MUSTs', function() {
  describe('3. Annotation Retrieval', function() {
    // TODO: get the first annotation from the first page of the container
    // and use that for these tests
    var annotation_url = 'anno1.jsonld';
    it('MUST support GET, HEAD, and OPTIONs methods (check Allow & GET)',
      function(done) {
        container
          .get(annotation_url)
          .expect('Allow', /GET/)
          .expect('Allow', /HEAD/)
          .expect('Allow', /OPTIONS/)
          .expect(200, done);
      }
    );

    it.skip('MUST support the JSON-LD representation using the Web Annotation profile',
      function(done) {
        // TODO: what's to check here not covered below? the `type` value?
      }
    );
    it('MUST have a Content-Type header with the application/ld+json media type',
      function(done) {
        container
          .get(annotation_url)
          .expect('Content-Type', MEDIA_TYPE_REGEX)
          .expect(200, done);
      }
    );
    it('MUST have a Link header entry where the target IRI is http://www.w3.org/ns/ldp#Resrouce and the rel parameter value is type',
      function(done) {
        // TODO: handle variations of Link headers
        var check = '<http://www.w3.org/ns/ldp#Resource>; rel="type"'
        container
          .get(annotation_url)
          .expect('Link', check)
          .expect(200, done);
      }
    );
    it.skip('MUST have an ETag header',
      function(done) {
        container
          .get(annotation_url)
          // wide open regex to catch anything...on purpose
          .expect('Etag', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST have an Allow header',
      function(done) {
        container
          .get(annotation_url)
          .expect('Allow', /GET/)
          .expect('Allow', /HEAD/)
          .expect('Allow', /OPTIONS/)
          .expect(200, done);
      }
    );
    it('MUST have a Vary header with Accept in the value',
      function(done) {
        container
          .get(annotation_url)
          .expect('Vary', /Accept/)
          .expect(200, done);
      }
    );
  });

  // Annotation Container Tests
  describe('4. Annotation Containers', function() {
    it.skip('An Annotation Server MUST provide one or more Containers');
    it('MUST end in a "/" character', function(done) {
      assert.isTrue(container_url[container_url.length-1] === '/');
      done();
    });

    container = request(container_url);
    it('MUST respond (by default) with an Annotation Container', function(done) {
      container
        .get('')
        .expect('Content-Type', MEDIA_TYPE_REGEX)
        .expect(200, done);
    });
  });

  describe('4.1 Container Retrieval', function() {
    container = request(container_url);
    it('MUST support GET, HEAD, and OPTIONs methods (check Allow & GET)',
      function(done) {
        container
          .get('')
          .expect('Allow', /GET/)
          .expect('Allow', /HEAD/)
          .expect('Allow', /OPTIONS/)
          .expect(200, done);
      }
    );
    it('MUST support GET, HEAD, and OPTIONs methods (check HEAD)',
      function(done) {
        container
          .head('')
          .expect(200, done);
      }
    );
    it('MUST support GET, HEAD, and OPTIONs methods (check OPTIONS)',
      function(done) {
        // Test OPTIONS method
        container
          .options('')
          .expect(200, done);
      }
    );
    it('MUST return a description of the container',
      function(done) {
        container
          .get('')
          .expect(/BasicContainer/)
          .expect(/AnnotationCollection/)
          .expect(200, done);
      }
    );
    it('MUST be available in JSON-LD',
      function(done) {
        container
          .get('')
          .set('Accept', MEDIA_TYPE)
          .expect('Content-Type', MEDIA_TYPE_REGEX)
          .expect(200, done);
      }
    );
    it('MUST return a Link header [rfc5988] on all responses',
      function(done) {
        container
          .get('')
          .expect('Link', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST advertise its type by including a link where the rel parameter'
        + ' value is type and the target IRI is the appropriate Container Type',
      function(done) {
        container
          .get('')
          .expect('Link', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST respond with a JSON-LD representation (by default)',
      function(done) {
        container
          .get('')
          .expect('Content-Type', MEDIA_TYPE_REGEX)
          .expect(200, done);
      }
    );
    it.skip('MUST include an Etag header',
      function(done) {
        container
          .get('')
          // wide open regex to catch anything...on purpose
          .expect('Etag', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST have a Vary header that includes Accept',
      function(done) {
        container
          .get('')
          .expect('Vary', /Accept/)
          .expect(200, done);
      }
    );
  });

  // TODO: update to new 4.2 Container Representations
  describe('4.1.3 Responses with Annotations', function() {
    it('MUST use the paged collection model', function(done) {
      container
        .get('')
        .expect(function(res) {
          if ('items' in res.body) {
            throw new Error('AnnotationCollection MUST NOT contain items'
                + ' directly; only link to pages');
          }
        })
        .expect(200, done)
    });
    it('MUST also have a link to the first page of its contents using first',
      function(done) {
        container
          .get('')
          .expect(function(res) {
            if (!('first' in res.body)) {
              throw new Error('AnnotationCollection MUST reference at least the'
                  + ' first AnnotationPage');
            }
          })
          .expect(200, done)
      }
    );
    it('MUST include a Content-Location header with the IRI as its value',
      function(done) {
        container
          .get('')
          .expect(function(res) {
            var cl = res.headers['content-location'];
            var id = res.body.id;
            if (cl !== id) {
              throw new Error('Content-Location and `id` MUST match');
            }
          })
          .expect(200, done)
      }
    );
    describe('Page Response', function() {
      it('MUST have a link to the container that it is part of, using the partOf property',
        function(done) {
          container
            .get('')
            // TODO: make this less...broad...
            .expect(/first/)
            .end(function(err, res) {
              if (err) throw err;
              request(host_url)
                .get(res.body.first)
                .expect(function(res) {
                  if (!('partOf' in res.body) || !('id' in res.body.partOf)) {
                    throw new Error('Paged responses must be `partOf` a collection');
                  }
                })
                .expect(200, done);
            });
        }
      );
      // If it is not the first page...
      it('MUST have a link to the previous page in the sequence, using the prev property (if not first page)',
        function(done) {
          // test the last page to be sure it has a previous (`prev`) page
          // TODO: containers SHOULD have a link to the last page...but may not
          container
            .get('')
            // TODO: make this less...broad...
            .expect(/last/)
            .end(function(err, res) {
              if (err) throw err;
              request(host_url)
                .get(res.body.last)
                .expect(function(res) {
                  if (!('prev' in res.body)) {
                    throw new Error('Must have a link to the previous page (if not first page)');
                  }
                })
                .expect(200, done);
            });
        }
      );
      it('MUST have a link to the next page in the sequence, using the'
          + ' next property (if not last page)',
        function(done) {
          // test the first page to be sure it has a next page
          container
            .get('')
            // TODO: make this less...broad...
            .expect(/first/)
            .end(function(err, res) {
              if (err) throw err;
              request(host_url)
                .get(res.body.first)
                .expect(function(res) {
                  // TODO: check "last page" scenario. See:
                  // https://github.com/BigBlueHat/web-annotation-protocol-tester/issues/4
                  if (!('next' in res.body)) {
                    throw new Error('Must have a link to the next page (if not last page)');
                  }
                })
                .expect(200, done);
            });
        }
      );
    });
  });
  describe('5. Creation, Updating and Deletion of Annotations', function() {
    describe('5.1 Create a New Annotation', function() {
      var makethis ={
        "@context": "http://www.w3.org/ns/anno.jsonld",
        "type": "Annotation",
        "body": {
          "type": "TextualBody",
          "value": "I like this page!"
        },
        "target": "http://www.example.com/index.html"
      };

      it('MUST assign an IRI to the Annotation resource in the id property,'
          + ' even if it already has one provided', function(done) {
        request(container_url)
          .post('')
          .set('Content-Type', MEDIA_TYPE)
          .send(makethis)
          .expect(function(res) {
            if (!('id' in res.body)) {
              throw new Error('The `id` must be set by the server.');
            }
          })
          .end(done);
      });

      it('MUST respond with a 201 Created response if the creation is successful',
        function(done) {
          request(container_url)
            .post('')
            .set('Content-Type', MEDIA_TYPE)
            .send(makethis)
            .expect(201, done);
        }
      );

      it("MUST have a Location header with the Annotation's new IRI",
        function(done) {
          request(container_url)
            .post('')
            .set('Content-Type', MEDIA_TYPE)
            .send(makethis)
            .expect(function(res) {
              if (res.body.id !== res.header['location']) {
                console.log(res.body.id);
                console.log(res.header['location']);
                throw new Error('Location header should match the id');
              }
            })
            .end(done);
        }
      );

      // "If the Annotation contains a canonical link, then it MUST be maintained without change."
      it("MUST maintain canonical without change",
        function(done) {
          makethis.canonical = "urn:uuid:" + uuid.v4();

          request(container_url)
            .post('')
            .set('Content-Type', MEDIA_TYPE)
            .send(makethis)
            .expect(function(res) {
              if (res.body.canonical !== makethis.canonical) {
                throw new Error('The canonical IRI must be preserved');
              }
            })
            .end(done);
        }
      );
    });
  });
});
<commit_msg>Add actual HEAD & OPTIONS test for annotations<commit_after>var assert = require('chai').assert;
var request = require('supertest');
var uuid = require('node-uuid');

var host_url = 'http://localhost:8080'
var container_url = host_url + '/annotations/';

// just ld+json here as the full profile'd media type is a SHOULD
const MEDIA_TYPE = 'application/ld+json';
const MEDIA_TYPE_REGEX = /application\/ld\+json/;

describe('MUSTs', function() {
  describe('3. Annotation Retrieval', function() {
    // TODO: get the first annotation from the first page of the container
    // and use that for these tests
    var annotation_url = 'anno1.jsonld';
    it('MUST support GET, HEAD, and OPTIONs methods (check Allow & GET)',
      function(done) {
        container
          .get(annotation_url)
          .expect('Allow', /GET/)
          .expect('Allow', /HEAD/)
          .expect('Allow', /OPTIONS/)
          .expect(200, done);
      }
    );
    it('MUST support GET, HEAD, and OPTIONs methods (check HEAD)',
      function(done) {
        container
          .head(annotation_url)
          .expect(200, done);
      }
    );
    it('MUST support GET, HEAD, and OPTIONs methods (check OPTIONS)',
      function(done) {
        // Test OPTIONS method
        container
          .options(annotation_url)
          .expect(200, done);
      }
    );
    it.skip('MUST support the JSON-LD representation using the Web Annotation profile',
      function(done) {
        // TODO: what's to check here not covered below? the `type` value?
      }
    );
    it('MUST have a Content-Type header with the application/ld+json media type',
      function(done) {
        container
          .get(annotation_url)
          .expect('Content-Type', MEDIA_TYPE_REGEX)
          .expect(200, done);
      }
    );
    it('MUST have a Link header entry where the target IRI is http://www.w3.org/ns/ldp#Resrouce and the rel parameter value is type',
      function(done) {
        // TODO: handle variations of Link headers
        var check = '<http://www.w3.org/ns/ldp#Resource>; rel="type"'
        container
          .get(annotation_url)
          .expect('Link', check)
          .expect(200, done);
      }
    );
    it.skip('MUST have an ETag header',
      function(done) {
        container
          .get(annotation_url)
          // wide open regex to catch anything...on purpose
          .expect('Etag', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST have an Allow header',
      function(done) {
        container
          .get(annotation_url)
          .expect('Allow', /GET/)
          .expect('Allow', /HEAD/)
          .expect('Allow', /OPTIONS/)
          .expect(200, done);
      }
    );
    it('MUST have a Vary header with Accept in the value',
      function(done) {
        container
          .get(annotation_url)
          .expect('Vary', /Accept/)
          .expect(200, done);
      }
    );
  });

  // Annotation Container Tests
  describe('4. Annotation Containers', function() {
    it.skip('An Annotation Server MUST provide one or more Containers');
    it('MUST end in a "/" character', function(done) {
      assert.isTrue(container_url[container_url.length-1] === '/');
      done();
    });

    container = request(container_url);
    it('MUST respond (by default) with an Annotation Container', function(done) {
      container
        .get('')
        .expect('Content-Type', MEDIA_TYPE_REGEX)
        .expect(200, done);
    });
  });

  describe('4.1 Container Retrieval', function() {
    container = request(container_url);
    it('MUST support GET, HEAD, and OPTIONs methods (check Allow & GET)',
      function(done) {
        container
          .get('')
          .expect('Allow', /GET/)
          .expect('Allow', /HEAD/)
          .expect('Allow', /OPTIONS/)
          .expect(200, done);
      }
    );
    it('MUST support GET, HEAD, and OPTIONs methods (check HEAD)',
      function(done) {
        container
          .head('')
          .expect(200, done);
      }
    );
    it('MUST support GET, HEAD, and OPTIONs methods (check OPTIONS)',
      function(done) {
        // Test OPTIONS method
        container
          .options('')
          .expect(200, done);
      }
    );
    it('MUST return a description of the container',
      function(done) {
        container
          .get('')
          .expect(/BasicContainer/)
          .expect(/AnnotationCollection/)
          .expect(200, done);
      }
    );
    it('MUST be available in JSON-LD',
      function(done) {
        container
          .get('')
          .set('Accept', MEDIA_TYPE)
          .expect('Content-Type', MEDIA_TYPE_REGEX)
          .expect(200, done);
      }
    );
    it('MUST return a Link header [rfc5988] on all responses',
      function(done) {
        container
          .get('')
          .expect('Link', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST advertise its type by including a link where the rel parameter'
        + ' value is type and the target IRI is the appropriate Container Type',
      function(done) {
        container
          .get('')
          .expect('Link', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST respond with a JSON-LD representation (by default)',
      function(done) {
        container
          .get('')
          .expect('Content-Type', MEDIA_TYPE_REGEX)
          .expect(200, done);
      }
    );
    it.skip('MUST include an Etag header',
      function(done) {
        container
          .get('')
          // wide open regex to catch anything...on purpose
          .expect('Etag', /(.*)/)
          .expect(200, done);
      }
    );
    it('MUST have a Vary header that includes Accept',
      function(done) {
        container
          .get('')
          .expect('Vary', /Accept/)
          .expect(200, done);
      }
    );
  });

  // TODO: update to new 4.2 Container Representations
  describe('4.1.3 Responses with Annotations', function() {
    it('MUST use the paged collection model', function(done) {
      container
        .get('')
        .expect(function(res) {
          if ('items' in res.body) {
            throw new Error('AnnotationCollection MUST NOT contain items'
                + ' directly; only link to pages');
          }
        })
        .expect(200, done)
    });
    it('MUST also have a link to the first page of its contents using first',
      function(done) {
        container
          .get('')
          .expect(function(res) {
            if (!('first' in res.body)) {
              throw new Error('AnnotationCollection MUST reference at least the'
                  + ' first AnnotationPage');
            }
          })
          .expect(200, done)
      }
    );
    it('MUST include a Content-Location header with the IRI as its value',
      function(done) {
        container
          .get('')
          .expect(function(res) {
            var cl = res.headers['content-location'];
            var id = res.body.id;
            if (cl !== id) {
              throw new Error('Content-Location and `id` MUST match');
            }
          })
          .expect(200, done)
      }
    );
    describe('Page Response', function() {
      it('MUST have a link to the container that it is part of, using the partOf property',
        function(done) {
          container
            .get('')
            // TODO: make this less...broad...
            .expect(/first/)
            .end(function(err, res) {
              if (err) throw err;
              request(host_url)
                .get(res.body.first)
                .expect(function(res) {
                  if (!('partOf' in res.body) || !('id' in res.body.partOf)) {
                    throw new Error('Paged responses must be `partOf` a collection');
                  }
                })
                .expect(200, done);
            });
        }
      );
      // If it is not the first page...
      it('MUST have a link to the previous page in the sequence, using the prev property (if not first page)',
        function(done) {
          // test the last page to be sure it has a previous (`prev`) page
          // TODO: containers SHOULD have a link to the last page...but may not
          container
            .get('')
            // TODO: make this less...broad...
            .expect(/last/)
            .end(function(err, res) {
              if (err) throw err;
              request(host_url)
                .get(res.body.last)
                .expect(function(res) {
                  if (!('prev' in res.body)) {
                    throw new Error('Must have a link to the previous page (if not first page)');
                  }
                })
                .expect(200, done);
            });
        }
      );
      it('MUST have a link to the next page in the sequence, using the'
          + ' next property (if not last page)',
        function(done) {
          // test the first page to be sure it has a next page
          container
            .get('')
            // TODO: make this less...broad...
            .expect(/first/)
            .end(function(err, res) {
              if (err) throw err;
              request(host_url)
                .get(res.body.first)
                .expect(function(res) {
                  // TODO: check "last page" scenario. See:
                  // https://github.com/BigBlueHat/web-annotation-protocol-tester/issues/4
                  if (!('next' in res.body)) {
                    throw new Error('Must have a link to the next page (if not last page)');
                  }
                })
                .expect(200, done);
            });
        }
      );
    });
  });
  describe('5. Creation, Updating and Deletion of Annotations', function() {
    describe('5.1 Create a New Annotation', function() {
      var makethis ={
        "@context": "http://www.w3.org/ns/anno.jsonld",
        "type": "Annotation",
        "body": {
          "type": "TextualBody",
          "value": "I like this page!"
        },
        "target": "http://www.example.com/index.html"
      };

      it('MUST assign an IRI to the Annotation resource in the id property,'
          + ' even if it already has one provided', function(done) {
        request(container_url)
          .post('')
          .set('Content-Type', MEDIA_TYPE)
          .send(makethis)
          .expect(function(res) {
            if (!('id' in res.body)) {
              throw new Error('The `id` must be set by the server.');
            }
          })
          .end(done);
      });

      it('MUST respond with a 201 Created response if the creation is successful',
        function(done) {
          request(container_url)
            .post('')
            .set('Content-Type', MEDIA_TYPE)
            .send(makethis)
            .expect(201, done);
        }
      );

      it("MUST have a Location header with the Annotation's new IRI",
        function(done) {
          request(container_url)
            .post('')
            .set('Content-Type', MEDIA_TYPE)
            .send(makethis)
            .expect(function(res) {
              if (res.body.id !== res.header['location']) {
                console.log(res.body.id);
                console.log(res.header['location']);
                throw new Error('Location header should match the id');
              }
            })
            .end(done);
        }
      );

      // "If the Annotation contains a canonical link, then it MUST be maintained without change."
      it("MUST maintain canonical without change",
        function(done) {
          makethis.canonical = "urn:uuid:" + uuid.v4();

          request(container_url)
            .post('')
            .set('Content-Type', MEDIA_TYPE)
            .send(makethis)
            .expect(function(res) {
              if (res.body.canonical !== makethis.canonical) {
                throw new Error('The canonical IRI must be preserved');
              }
            })
            .end(done);
        }
      );
    });
  });
});
<|endoftext|>


<commit_before>/*
 * Copyright 2013 Mirantis, Inc.
 *
 * 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.
 **/
casper.start().authenticate().skipWelcomeScreen();
casper.createCluster({name: 'Test Cluster'});
var nodes = [
    {status: 'discover', manufacturer: 'Dell', mac: 'C0:8D:DF:52:76:F1'}
];
nodes.forEach(function(node) {
    casper.createNode(node);
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing cluster page');
    this.test.assertExists('.deploy-btn:disabled', 'Deploy changes button is disabled');
    this.test.assertExists('.btn-configure-disks:disabled', 'Button Configure Disks is disabled');
    this.test.assertExists('.btn-configure-interfaces:disabled', 'Button Configure Interfaces is disabled');
    this.test.assertExists('.btn-add-nodes:not(:disabled)', 'Add Nodes button is enabled');
    this.test.assertEvalEquals(function() {return $('.node-box').length}, 0, 'Number of environment nodes is correct');
});

casper.then(function() {
    this.test.comment('Testing adding node with controller role');
    this.click('.btn-add-nodes');
    this.test.assertSelectorAppears('.node-box', 'Add Nodes screen appeared and unallocated nodes loaded');
    this.then(function() {
        this.test.assertEvalEquals(function() {return $('.node-box .node-content').length}, 1, 'Number of unallocated nodes is correct');
        this.click('input[name="controller"]');
        this.click('.node input[type=checkbox]');
        this.test.assertSelectorAppears('.pending_roles', 'Controller role is applied to the node');
    });
    this.then(function() {
        this.click('.btn-apply');
        this.test.assertSelectorDisappears('.role-panel', 'Return to nodes tab');
    });
    this.then(function() {
        this.test.assertEvalEquals(function() {return $('.node-box').length}, 1, 'Number of available roles is correct');
        this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled now');
    });
});

casper.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');

casper.then(function() {
    this.test.comment('Testing Reset button exist and disabled');
    this.test.assertExists('.action-item-controls .reset-environment-btn:disabled', 'Reset button exist and disabled');
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing stop deployment');
    this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled');
    this.click('.deploy-btn'); // "Deploy changes" button click
    this.test.assertSelectorAppears('.modal', 'Deployment dialog opens');
    this.then(function() {
        this.click('.modal .start-deployment-btn'); // "Start Deployment" button click
    });
    this.test.assertSelectorDisappears('.modal', 'Deployment dialog closes after clicking Start Deployment');
    this.test.assertSelectorDisappears('.deploy-btn', 'Deploy changes button disappears');
    this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-success', 'Deployment progress-success bar appears');
    this.test.assertSelectorAppears('.stop-deployment-btn', 'Stop deployment button appears');
    this.then(function() {
        this.click('.stop-deployment-btn'); // "Stop Deployment" button click
    });
    this.test.assertSelectorAppears('.modal', 'Stop Deployment dialog opens');
    this.then(function() {
        this.click('.modal .stop-deployment-btn'); // "Stop Deployment" on modal screen button click
    });
    this.then(function() {
        this.test.assertSelectorDisappears('.modal', 'Stop deployment dialog closes after clicking Stop button');
        this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-striped', 'Deployment progress-stopping bar appears');
        this.test.assertSelectorDisappears('.cluster-deploy-placeholder .progress-striped', 'Deployment progress-stopping bar disappears', 60000);
    });
    this.then(function() {
        this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled again');
        this.test.assertExists('.alert-success', 'Success stopping deployment alert message appears');
    });
    this.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');
    this.then(function() {
        this.test.assertExists('.action-item-controls .reset-environment-btn:not(:disabled)', 'Reset button exist and enabled after stopped deployment');
    });
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing Tabs locking after deployment stop and there are no Ready Nodes');
    this.test.assertEvalEquals(function() {return $('.node-box.ready').length}, 0, 'Number of ready nodes is correct');
    this.loadPage('#cluster/1/network').waitForSelector('#tab-network > *');
    this.then(function() {
        this.test.assertExists('.network-settings:not(.changes-locked)', 'Network Tab is unlocked');
    });
    this.loadPage('#cluster/1/settings').waitForSelector('#tab-settings > *');
    this.then(function() {
        this.test.assertExists('.openstack-settings:not(.changes-locked)', 'Settings Tab is unlocked');
    });
    // TODO: add "Health Check" Tab checking - it should be locked in this scenario
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing deployment process');
    this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled');
    this.click('.deploy-btn'); // "Deploy changes" button click
    this.test.assertSelectorAppears('.modal', 'Deployment dialog opens');
    this.then(function() {
        this.click('.modal .start-deployment-btn'); // "Start Deployment" button click
    });
    this.test.assertSelectorDisappears('.modal', 'Deployment dialog closes after clicking Start Deployment');
    this.test.assertSelectorDisappears('.deploy-btn', 'Deploy changes button disappears');
    this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-success', 'Deployment progress-success bar appears');
    this.test.assertSelectorAppears('.stop-deployment-btn', 'Stop deployment button appears');
    this.waitForSelector('.node-box.ready', function() { // We are waiting till node become ready
        this.test.assertEvalEquals(function() {return $('.node-box.ready').length}, 1, 'Number of ready nodes is correct');
    }, function() { this.test.comment("Timeout reached"); }, 60000);
    this.test.assertSelectorDisappears('.cluster-deploy-placeholder .progress-success', 'Deployment progress-stopping bar disappears', 60000);
    this.then(function() {
        this.test.assertExists('.alert-success', 'Success deployment process finish message appears');
        this.test.assertExists('.deploy-btn:disabled', 'Deploy changes button is disabled');
        var countIsReadyNodes = this.evaluate(function() { // get count of Nodes with Ready status
            return $('.node-box.ready').length;
        });
        this.test.assertEvalEquals(function() {return $('.node-box .node-content').length}, countIsReadyNodes, 'All nodes in list are ready');
    });
});

casper.then(function() {
    this.test.comment('Testing Tabs locking after deployment finished completely');
    this.loadPage('#cluster/1/network').waitForSelector('#tab-network > *');
    this.then(function() {
        this.test.assertExists('.network-settings.changes-locked', 'Network Tab is Locked');
    });
    this.loadPage('#cluster/1/settings').waitForSelector('#tab-settings > *');
    this.then(function() {
        this.test.assertExists('.openstack-settings.changes-locked', 'Settings Tab is Locked');
    });
    // TODO: add "Health Check" Tab checking - it should be Unlocked in this scenario
});

casper.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');

casper.then(function() {
    this.test.comment('Testing Reset button');
    this.test.assertExists('.action-item-controls .reset-environment-btn:not(:disabled)', 'Reset button exist and enabled after successful finished deployment');
    this.click('.action-item-controls .reset-environment-btn'); // "Reset" button click
    this.test.assertSelectorAppears('.modal', 'Reset dialog opens');
    this.then(function() {
        this.click('.modal .reset-environment-btn'); // "Reset environment" button click
    });
    this.test.assertSelectorDisappears('.modal', 'Reset dialog closes after clicking Reset button');
    this.test.assertSelectorDisappears('.deploy-btn', 'Deploy changes button disappears');
    this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-striped', 'Reset progress bar appears');
    this.test.assertSelectorDisappears('.cluster-deploy-placeholder .progress-striped', 'Reset progress bar disappears', 60000);
    this.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');
    this.then(function() {
        this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled again');
        this.test.assertExists('.alert-success', 'Success reset message appears');
        this.test.assertEvalEquals(function() {return $('.node-box .node-content').length}, 1, 'Number of count nodes is correct');
        this.test.assertEvalEquals(function() {return $('.node-box.ready').length}, 0, 'Number of ready nodes is correct');
    });
    this.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');
    this.then(function() {
        this.test.assertSelectorAppears('.action-item-controls .reset-environment-btn:disabled', 'Reset button is disabled after successful reset');
    });
});

casper.run(function() {
    this.test.done();
});
<commit_msg>Fix for random fail deploy ui button test<commit_after>/*
 * Copyright 2013 Mirantis, Inc.
 *
 * 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.
 **/
casper.start().authenticate().skipWelcomeScreen();
casper.createCluster({name: 'Test Cluster'});
var nodes = [
    {status: 'discover', manufacturer: 'Dell', mac: 'C0:8D:DF:52:76:F1'}
];
nodes.forEach(function(node) {
    casper.createNode(node);
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing cluster page');
    this.test.assertExists('.deploy-btn:disabled', 'Deploy changes button is disabled');
    this.test.assertExists('.btn-configure-disks:disabled', 'Button Configure Disks is disabled');
    this.test.assertExists('.btn-configure-interfaces:disabled', 'Button Configure Interfaces is disabled');
    this.test.assertExists('.btn-add-nodes:not(:disabled)', 'Add Nodes button is enabled');
    this.test.assertEvalEquals(function() {return $('.node-box').length}, 0, 'Number of environment nodes is correct');
});

casper.then(function() {
    this.test.comment('Testing adding node with controller role');
    this.click('.btn-add-nodes');
    this.test.assertSelectorAppears('.node-box', 'Add Nodes screen appeared and unallocated nodes loaded');
    this.then(function() {
        this.test.assertEvalEquals(function() {return $('.node-box .node-content').length}, 1, 'Number of unallocated nodes is correct');
        this.click('input[name="controller"]');
        this.click('.node input[type=checkbox]');
        this.test.assertSelectorAppears('.pending_roles', 'Controller role is applied to the node');
    });
    this.then(function() {
        this.click('.btn-apply');
        this.test.assertSelectorDisappears('.role-panel', 'Return to nodes tab');
    });
    this.then(function() {
        this.test.assertEvalEquals(function() {return $('.node-box').length}, 1, 'Number of available roles is correct');
        this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled now');
    });
});

casper.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');

casper.then(function() {
    this.test.comment('Testing Reset button exist and disabled');
    this.test.assertExists('.action-item-controls .reset-environment-btn:disabled', 'Reset button exist and disabled');
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing stop deployment');
    this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled');
    this.click('.deploy-btn'); // "Deploy changes" button click
    this.test.assertSelectorAppears('.modal', 'Deployment dialog opens');
    this.then(function() {
        this.click('.modal .start-deployment-btn'); // "Start Deployment" button click
    });
    this.test.assertSelectorDisappears('.modal', 'Deployment dialog closes after clicking Start Deployment');
    this.test.assertSelectorDisappears('.deploy-btn', 'Deploy changes button disappears');
    this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-success', 'Deployment progress-success bar appears');
    this.test.assertSelectorAppears('.stop-deployment-btn', 'Stop deployment button appears');
    this.then(function() {
        this.click('.stop-deployment-btn'); // "Stop Deployment" button click
    });
    this.test.assertSelectorAppears('.modal', 'Stop Deployment dialog opens');
    this.then(function() {
        this.click('.modal .stop-deployment-btn'); // "Stop Deployment" on modal screen button click
    });
    this.then(function() {
        this.test.assertSelectorDisappears('.modal', 'Stop deployment dialog closes after clicking Stop button');
        this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-striped', 'Deployment progress-stopping bar appears');
        this.test.assertSelectorDisappears('.cluster-deploy-placeholder .progress-striped', 'Deployment progress-stopping bar disappears', 60000);
    });
    this.then(function() {
        this.test.assertSelectorAppears('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled again');
        this.test.assertSelectorAppears('.alert-success', 'Success stopping deployment alert message appears');
    });
    this.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');
    this.then(function() {
        this.test.assertExists('.action-item-controls .reset-environment-btn:not(:disabled)', 'Reset button exist and enabled after stopped deployment');
    });
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing Tabs locking after deployment stop and there are no Ready Nodes');
    this.test.assertEvalEquals(function() {return $('.node-box.ready').length}, 0, 'Number of ready nodes is correct');
    this.loadPage('#cluster/1/network').waitForSelector('#tab-network > *');
    this.then(function() {
        this.test.assertExists('.network-settings:not(.changes-locked)', 'Network Tab is unlocked');
    });
    this.loadPage('#cluster/1/settings').waitForSelector('#tab-settings > *');
    this.then(function() {
        this.test.assertExists('.openstack-settings:not(.changes-locked)', 'Settings Tab is unlocked');
    });
    // TODO: add "Health Check" Tab checking - it should be locked in this scenario
});

casper.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');

casper.then(function() {
    this.test.comment('Testing deployment process');
    this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled');
    this.click('.deploy-btn'); // "Deploy changes" button click
    this.test.assertSelectorAppears('.modal', 'Deployment dialog opens');
    this.then(function() {
        this.click('.modal .start-deployment-btn'); // "Start Deployment" button click
    });
    this.test.assertSelectorDisappears('.modal', 'Deployment dialog closes after clicking Start Deployment');
    this.test.assertSelectorDisappears('.deploy-btn', 'Deploy changes button disappears');
    this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-success', 'Deployment progress-success bar appears');
    this.test.assertSelectorAppears('.stop-deployment-btn', 'Stop deployment button appears');
    this.waitForSelector('.node-box.ready', function() { // We are waiting till node become ready
        this.test.assertEvalEquals(function() {return $('.node-box.ready').length}, 1, 'Number of ready nodes is correct');
    }, function() { this.test.comment("Timeout reached"); }, 60000);
    this.test.assertSelectorDisappears('.cluster-deploy-placeholder .progress-success', 'Deployment progress-stopping bar disappears', 60000);
    this.then(function() {
        this.test.assertExists('.alert-success', 'Success deployment process finish message appears');
        this.test.assertExists('.deploy-btn:disabled', 'Deploy changes button is disabled');
        var countIsReadyNodes = this.evaluate(function() { // get count of Nodes with Ready status
            return $('.node-box.ready').length;
        });
        this.test.assertEvalEquals(function() {return $('.node-box .node-content').length}, countIsReadyNodes, 'All nodes in list are ready');
    });
});

casper.then(function() {
    this.test.comment('Testing Tabs locking after deployment finished completely');
    this.loadPage('#cluster/1/network').waitForSelector('#tab-network > *');
    this.then(function() {
        this.test.assertExists('.network-settings.changes-locked', 'Network Tab is Locked');
    });
    this.loadPage('#cluster/1/settings').waitForSelector('#tab-settings > *');
    this.then(function() {
        this.test.assertExists('.openstack-settings.changes-locked', 'Settings Tab is Locked');
    });
    // TODO: add "Health Check" Tab checking - it should be Unlocked in this scenario
});

casper.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');

casper.then(function() {
    this.test.comment('Testing Reset button');
    this.test.assertExists('.action-item-controls .reset-environment-btn:not(:disabled)', 'Reset button exist and enabled after successful finished deployment');
    this.click('.action-item-controls .reset-environment-btn'); // "Reset" button click
    this.test.assertSelectorAppears('.modal', 'Reset dialog opens');
    this.then(function() {
        this.click('.modal .reset-environment-btn'); // "Reset environment" button click
    });
    this.test.assertSelectorDisappears('.modal', 'Reset dialog closes after clicking Reset button');
    this.test.assertSelectorDisappears('.deploy-btn', 'Deploy changes button disappears');
    this.test.assertSelectorAppears('.cluster-deploy-placeholder .progress-striped', 'Reset progress bar appears');
    this.test.assertSelectorDisappears('.cluster-deploy-placeholder .progress-striped', 'Reset progress bar disappears', 60000);
    this.loadPage('#cluster/1/nodes').waitForSelector('#tab-nodes > *');
    this.then(function() {
        this.test.assertExists('.deploy-btn:not(:disabled)', 'Deploy changes button is enabled again');
        this.test.assertExists('.alert-success', 'Success reset message appears');
        this.test.assertEvalEquals(function() {return $('.node-box .node-content').length}, 1, 'Number of count nodes is correct');
        this.test.assertEvalEquals(function() {return $('.node-box.ready').length}, 0, 'Number of ready nodes is correct');
    });
    this.loadPage('#cluster/1/actions').waitForSelector('#tab-actions > *');
    this.then(function() {
        this.test.assertSelectorAppears('.action-item-controls .reset-environment-btn:disabled', 'Reset button is disabled after successful reset');
    });
});

casper.run(function() {
    this.test.done();
});
<|endoftext|>


<commit_before>import React from 'react';
import { Link } from '@curi/react';

import BaseTutorial from './base/BaseTutorial';
import { TutorialBranch } from '../components/TutorialBranch';
import {
  InlineJS as IJS,
  InlineComponent as Cmp,
  PrismBlock
} from '../components/PrismBlocks';
import { Note } from '../components/Messages';
import { Section } from '../components/Sections';

export default () => (
  <BaseTutorial>

Part 2: Setting up Curi

      Setup is never exciting, but a little bit of it is necessary.

        In this tutorial, we will be doing the following:

          Initializing our project and installing the packages (from NPM)
          that are necessary to get started.

          Creating directories for our source and public files, as well
          as adding some empty files for future use.

          Setting up a simple Express server to serve our website
          contents.

          Setting up Babel/Webpack.

    <TutorialBranch name='02-setup' />

        In the terminal, navigate to the parent directory where you want to keep
        the code for your website and create a new directory for the website. We
        can then navigate into our new directory.

      <PrismBlock lang='bash'>
        {
`# if you're using the tutorial repo, you should skip
# this because you'll be using the repo folder
mkdir curi-bookstore
cd curi-bookstore`
        }
      </PrismBlock>

        Inside of the directory, run <IJS>npm init</IJS> to generate a{' '}
        <IJS>package.json</IJS> file. Then, we can install the most important
        package for Curi: <IJS>@curi/core</IJS>. We should also install{' '}
        <IJS>@hickory/browser</IJS>, which we'll cover later on.

      <PrismBlock lang='bash'>
        {
`npm init
npm install @curi/core @hickory/browser`
        }
      </PrismBlock>

        We should also get some of the directories/files for our website setup.
        You can restructure your application however you want, but the approach
        used in this project can serve as a good starting point.

      <PrismBlock lang='bash'>
        {
`mkdir src
touch src/index.js src/routes.js
mkdir -p public/js
touch public/index.html`
        }
      </PrismBlock>

        The above commands will leave us with the following project structure:

      <PrismBlock>
        {
`curi-bookstore/
+- src/
|  +- index.js
|  +- routes.js
+- public/
|  +- index.html
|  +- js/
+- package.json`
        }
      </PrismBlock>

        Our website needs a server that can respond to dynamic requests. For this, we
        will use express.

      <PrismBlock lang='bash'>
        {
`npm install --save-dev express
touch server.js`
        }
      </PrismBlock>

        Our server isn't very fancy. For static file requests, it serves files
        from the <IJS>/public</IJS> directory. For everything else, it will serve
        the <IJS>/public/index.html</IJS> file. You can just copy the following
        code into <IJS>server.js</IJS>.

      <PrismBlock lang='javascript'>
        {
`const express = require('express');
const path = require('path');

const STATIC_DIR = path.join(__dirname, 'public');
const INDEX_HTML = path.join(STATIC_DIR, 'index.html');

const app = express();

// respond to requests for files that begin with /static
// with the corresponding file in the /public directory
app.use('/static', express.static(STATIC_DIR));

// all other requests will respond with the index.html file
app.get('*', (req, res) => {
  res.sendFile(INDEX_HTML);
});

app.listen('8000', () => {
  console.log('Server started. Listening on port 8000');
});`
        }
      </PrismBlock>

        To run the server, you just need to run <IJS>node server.js</IJS>.

        Our <IJS>index.html</IJS> file is a simple HTML file with with
        a <Cmp>script</Cmp> that loads our bundled application. You can copy
        the code below into your <IJS>public/index.html</IJS> file.

      <PrismBlock lang='html'>
        {
`<!doctype html>

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

`
        }
      </PrismBlock>

        We will be using Babel/Webpack to build our project. We won't be
        diving into the details of how these work, but will provide the
        code necessary to get this setup.

        We can start by installing the necessary packages as well as adding
        configuration files for both Babel and Webpack. We will be using
        Babel 7, so we can use a JavaScript file instead of JSON for our
        Babel config.

      <PrismBlock lang='bash'>
        {
`npm install -D webpack @babel/core@next @babel/preset-env@next babel-loader@next
touch .babelrc.js webpack.config.js`
        }
      </PrismBlock>
      <PrismBlock lang='javascript'>
        {
`// .babelrc.js
module.exports = {
  presets: [
    ['@babel/env',{
      modules: false
    }]
  ]
};`
        }
      </PrismBlock>
      <PrismBlock lang='javascript'>
        {
`// webpack.config.js
const path = require('path');

const config = {
  context: path.resolve(__dirname, 'src'),
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'js/bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      }
    ]
  }
}

module.exports = config;`
        }
      </PrismBlock>

        Finally, we just need to add a scripts to our{' '}
        <IJS>package.json</IJS> file in order to build our bundle.

      <PrismBlock lang='javascript'>
        {
`// package.json
{
  // ...,
  "scripts": {
    "build": "webpack"
  }
}`
        }
      </PrismBlock>

        With our project setup, we are now ready to continue the tutorial with{' '}
        <Link to='Tutorial' params={{ name: '03-routes' }}>Part 3: Routes</Link>.

  </BaseTutorial>
);
<commit_msg>(docs) Use webpack-dev-server [ci skip]<commit_after>import React from 'react';
import { Link } from '@curi/react';

import BaseTutorial from './base/BaseTutorial';
import { TutorialBranch } from '../components/TutorialBranch';
import {
  InlineJS as IJS,
  InlineComponent as Cmp,
  PrismBlock
} from '../components/PrismBlocks';
import { Note } from '../components/Messages';
import { Section } from '../components/Sections';

export default () => (
  <BaseTutorial>

Part 2: Setting up Curi

      Setup is never exciting, but a little bit of it is necessary.

        In this tutorial, we will be doing the following:

          Initializing our project and installing the packages (from NPM)
          that are necessary to get started.

          Creating directories for our source and public files, as well
          as adding some empty files for future use.

          Setting up a simple Express server to serve our website
          contents.

          Setting up Babel/Webpack.

    <TutorialBranch name='02-setup' />

        In the terminal, navigate to the parent directory where you want to keep
        the code for your website and create a new directory for the website. We
        can then navigate into our new directory.

      <PrismBlock lang='bash'>
        {
`# if you're using the tutorial repo, you should skip
# this because you'll be using the repo folder
mkdir curi-bookstore
cd curi-bookstore`
        }
      </PrismBlock>

        Inside of the directory, run <IJS>npm init</IJS> to generate a{' '}
        <IJS>package.json</IJS> file. Then, we can install the most important
        package for Curi: <IJS>@curi/core</IJS>. We should also install{' '}
        <IJS>@hickory/browser</IJS>, which we'll cover later on.

      <PrismBlock lang='bash'>
        {
`npm init
npm install @curi/core @hickory/browser`
        }
      </PrismBlock>

        We should also get some of the directories/files for our website setup.
        You can restructure your application however you want, but the approach
        used in this project can serve as a good starting point.

      <PrismBlock lang='bash'>
        {
`mkdir src
touch src/index.js src/routes.js
mkdir -p public/js
touch public/index.html`
        }
      </PrismBlock>

        The above commands will leave us with the following project structure:

      <PrismBlock>
        {
`curi-bookstore/
+- src/
|  +- index.js
|  +- routes.js
+- public/
|  +- index.html
|  +- js/
+- package.json`
        }
      </PrismBlock>

        We will be using Babel/Webpack to build our project. We won't be
        diving into the details of how these work, but will provide the
        code necessary to get this setup.

        We can start by installing the necessary packages as well as adding
        configuration files for both Babel and Webpack. We will be using
        Babel 7, so we can use a JavaScript file instead of JSON for our
        Babel config.

        We will also be using <IJS>webpack-dev-server</IJS> in place of
        setting up a server. This allows us to have live reloading
        in development.

      <PrismBlock lang='bash'>
        {
`touch .babelrc.js webpack.config.js
npm install -D webpack webpack-dev-server @babel/core@next
  @babel/preset-env@next babel-loader@next`
        }
      </PrismBlock>
      <PrismBlock lang='javascript'>
        {
`// .babelrc.js
module.exports = {
  presets: [
    ['@babel/env',{
      modules: false
    }]
  ]
};`
        }
      </PrismBlock>
      <PrismBlock lang='javascript'>
        {
`// webpack.config.js
const path = require('path');

const config = {
  context: path.resolve(__dirname, 'src'),
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'js/bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      }
    ]
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'public')
  }
}

module.exports = config;`
        }
      </PrismBlock>

        We also need to create the <IJS>index.html</IJS> file. This just needs
        to include the DOM element that we will render our application in and
        a <Cmp>script</Cmp> that loads our bundled application. You can copy
        the code below into your <IJS>public/index.html</IJS> file.

      <PrismBlock lang='html'>
        {
`<!doctype html>

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

`
        }
      </PrismBlock>

        Finally, we just need to add a script to our{' '}
        <IJS>package.json</IJS> file in order to build our bundle.

      <PrismBlock lang='javascript'>
        {
`// package.json
{
  // ...,
  "scripts": {
    "dev": "webpack-dev-server"
  }
}`
        }
      </PrismBlock>
      <Note>
        If you're building this from scratch, you should also add a{' '}
        <IJS>.gitignore</IJS> file and ignore the <IJS>node_modules/</IJS>
        {' '}directory.
      </Note>

        With our project setup, we are now ready to continue the tutorial with{' '}
        <Link to='Tutorial' params={{ name: '03-routes' }}>Part 3: Routes</Link>.

  </BaseTutorial>
);
<|endoftext|>


<commit_before>var expect        = require('expect.js');
var _             = require('lodash');
var sampleData    = require('./lib/sampleData');
var fruits        = sampleData.fruits;
var fns           = sampleData.fns;
var helpers       = require('./lib/helpers');
var setupModel    = helpers.getModelSetup(fns, '*', ['name', 'color']);

describe('Query', function () {
  describe('Basic functionality', function () {
    it('Returns list from non-empty collection querying for everything', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('yellowFruits');
      var query = view.query('a', 'z');
      query.ref('_page.filteredFruits');
      var expectedFruits = model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true});
      expect(model.get('filteredFruits')).to.eql(expectedFruits);
    });

    it('Returns specific set of items when querying for it', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('colorOrganized');
      var query = view.query('yellow', 'yellowz');
      query.ref('_page.yellowFruits');
      var expectedFruits = model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true});
      expect(model.get('yellowFruits')).to.eql(expectedFruits);
    });
  });

  describe('Updating item in collection', function () {
    it('Correct order after changing relevant property', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('yellowFruits');
      var query = view.query('a', 'z');
      query.ref('_page.filteredFruits');
      model.set('fruits.bananaId.name', 'weird-banana');
      var expectedFruits = model.expectedResult({fruits: ['lemonId', 'bananaId']}, {array: true});
      expect(model.get('filteredFruits')).to.eql(expectedFruits);
    });
  });

  describe('Multi-level organized', function () {
    it('Returns single category/specific set of items when querying for it', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('colorOrganizedMultilevel');
      var query = view.query('yellow', 'yellowz');
      query.ref('_page.yellowFruits');
      var expectedFruits = model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true});
      expect(model.get('yellowFruits')).to.eql(expectedFruits);
    });

    describe('Query per level', function () {
      it('Returns the appropriate lists', function () {
        var model = setupModel({fruits: fruits});
        var view = model.at('fruits').view('colorOrganizedMultilevel');
        view.queryPerLevel('_page.fruitLists');
        var expectedFruits = {
          orange: model.expectedResult({fruits: ['orangeId']}, {array: true}),
          red: model.expectedResult({fruits: ['appleId']}, {array: true}),
          yellow: model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true})
        };
        expect(model.get('fruitLists')).to.eql(expectedFruits);
      });

      describe('Updating item in collection', function () {
        it('Correct order after changing relevant property', function () {
          var model = setupModel({fruits: fruits});
          var view = model.at('fruits').view('colorOrganizedMultilevel');
          view.queryPerLevel('_page.fruitLists');
          model.set('fruits.bananaId.name', 'weird-banana');
          var expectedFruits = {
            orange: model.expectedResult({fruits: ['orangeId']}, {array: true}),
            red: model.expectedResult({fruits: ['appleId']}, {array: true}),
            yellow: model.expectedResult({fruits: ['lemonId', 'bananaId']}, {array: true})
          };
          expect(model.get('fruitLists')).to.eql(expectedFruits);
        });

        it('Item moved category after changing relevant property', function () {
          var model = setupModel({fruits: fruits});
          var view = model.at('fruits').view('colorOrganizedMultilevel');
          view.queryPerLevel('_page.fruitLists');
          model.set('fruits.bananaId.color', 'orange');
          var expectedFruits = {
            orange: model.expectedResult({fruits: ['bananaId', 'orangeId']}, {array: true}),
            red: model.expectedResult({fruits: ['appleId']}, {array: true}),
            yellow: model.expectedResult({fruits: ['lemonId']}, {array: true})
          };
          expect(model.get('fruitLists')).to.eql(expectedFruits);
        });

        it('Item moved to new category after changing relevant property', function () {
          var model = setupModel({fruits: fruits});
          var view = model.at('fruits').view('colorOrganizedMultilevel');
          view.queryPerLevel('_page.fruitLists');
          model.set('fruits.bananaId.color', 'brown');
          var expectedFruits = {
            brown: model.expectedResult({fruits: ['bananaId']}, {array: true}),
            orange: model.expectedResult({fruits: ['orangeId']}, {array: true}),
            red: model.expectedResult({fruits: ['appleId']}, {array: true}),
            yellow: model.expectedResult({fruits: ['lemonId']}, {array: true})
          };
          expect(model.get('fruitLists')).to.eql(expectedFruits);
        });
      });
    });
  });
});
<commit_msg>Add multi level query test highlighting issue with moving category, causing an empty category<commit_after>var expect        = require('expect.js');
var _             = require('lodash');
var sampleData    = require('./lib/sampleData');
var fruits        = sampleData.fruits;
var fns           = sampleData.fns;
var helpers       = require('./lib/helpers');
var setupModel    = helpers.getModelSetup(fns, '*', ['name', 'color']);

describe('Query', function () {
  describe('Basic functionality', function () {
    it('Returns list from non-empty collection querying for everything', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('yellowFruits');
      var query = view.query('a', 'z');
      query.ref('_page.filteredFruits');
      var expectedFruits = model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true});
      expect(model.get('filteredFruits')).to.eql(expectedFruits);
    });

    it('Returns specific set of items when querying for it', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('colorOrganized');
      var query = view.query('yellow', 'yellowz');
      query.ref('_page.yellowFruits');
      var expectedFruits = model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true});
      expect(model.get('yellowFruits')).to.eql(expectedFruits);
    });
  });

  describe('Updating item in collection', function () {
    it('Correct order after changing relevant property', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('yellowFruits');
      var query = view.query('a', 'z');
      query.ref('_page.filteredFruits');
      model.set('fruits.bananaId.name', 'weird-banana');
      var expectedFruits = model.expectedResult({fruits: ['lemonId', 'bananaId']}, {array: true});
      expect(model.get('filteredFruits')).to.eql(expectedFruits);
    });
  });

  describe('Multi-level organized', function () {
    it('Returns single category/specific set of items when querying for it', function () {
      var model = setupModel({fruits: fruits});
      var view = model.at('fruits').view('colorOrganizedMultilevel');
      var query = view.query('yellow', 'yellowz');
      query.ref('_page.yellowFruits');
      var expectedFruits = model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true});
      expect(model.get('yellowFruits')).to.eql(expectedFruits);
    });

    describe('Query per level', function () {
      it('Returns the appropriate lists', function () {
        var model = setupModel({fruits: fruits});
        var view = model.at('fruits').view('colorOrganizedMultilevel');
        view.queryPerLevel('_page.fruitLists');
        var expectedFruits = {
          orange: model.expectedResult({fruits: ['orangeId']}, {array: true}),
          red: model.expectedResult({fruits: ['appleId']}, {array: true}),
          yellow: model.expectedResult({fruits: ['bananaId', 'lemonId']}, {array: true})
        };
        expect(model.get('fruitLists')).to.eql(expectedFruits);
      });

      describe('Updating item in collection', function () {
        it('Correct order after changing relevant property', function () {
          var model = setupModel({fruits: fruits});
          var view = model.at('fruits').view('colorOrganizedMultilevel');
          view.queryPerLevel('_page.fruitLists');
          model.set('fruits.bananaId.name', 'weird-banana');
          var expectedFruits = {
            orange: model.expectedResult({fruits: ['orangeId']}, {array: true}),
            red: model.expectedResult({fruits: ['appleId']}, {array: true}),
            yellow: model.expectedResult({fruits: ['lemonId', 'bananaId']}, {array: true})
          };
          expect(model.get('fruitLists')).to.eql(expectedFruits);
        });

        it('Item moved category after changing relevant property', function () {
          var model = setupModel({fruits: fruits});
          var view = model.at('fruits').view('colorOrganizedMultilevel');
          view.queryPerLevel('_page.fruitLists');
          model.set('fruits.bananaId.color', 'orange');
          var expectedFruits = {
            orange: model.expectedResult({fruits: ['bananaId', 'orangeId']}, {array: true}),
            red: model.expectedResult({fruits: ['appleId']}, {array: true}),
            yellow: model.expectedResult({fruits: ['lemonId']}, {array: true})
          };
          expect(model.get('fruitLists')).to.eql(expectedFruits);
        });

        it('Item moved to new category after changing relevant property', function () {
          var model = setupModel({fruits: fruits});
          var view = model.at('fruits').view('colorOrganizedMultilevel');
          view.queryPerLevel('_page.fruitLists');
          model.set('fruits.bananaId.color', 'brown');
          var expectedFruits = {
            brown: model.expectedResult({fruits: ['bananaId']}, {array: true}),
            orange: model.expectedResult({fruits: ['orangeId']}, {array: true}),
            red: model.expectedResult({fruits: ['appleId']}, {array: true}),
            yellow: model.expectedResult({fruits: ['lemonId']}, {array: true})
          };
          expect(model.get('fruitLists')).to.eql(expectedFruits);
        });

        it('Item moved to category leaving empty beind after changing relevant property', function () {
          var model = setupModel({fruits: fruits});
          var view = model.at('fruits').view('colorOrganizedMultilevel');
          view.queryPerLevel('_page.fruitLists');
          model.set('fruits.orangeId.color', 'yellow');
          var expectedFruits = {
            red: model.expectedResult({fruits: ['appleId']}, {array: true}),
            yellow: model.expectedResult({fruits: ['bananaId', 'lemonId', 'orangeId']}, {array: true})
          };
          expect(model.get('fruitLists')).to.eql(expectedFruits);
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>var width = 960,
	height = 800,
	innerRadius = Math.min(width, height) * .41,
	outerRadius = innerRadius * 1.1;

var fill = d3.scale.ordinal().domain(d3.range(5)).range(
		[ "#000000", "#FFDD89", "#957244", "#F26223", "#00FF00" ]);

function fillByLength(length){
	if(length < 10000){
		return "#FF0000";
	}
	else if(length < 50000){
		return "#FFDD00";
	}
	else{
		return "#00FF00";
	}
}

function fillByIdy(identity){
	if(identity < 90){
		return "#FF0000";
	}
	else if(identity < 95){
		return "#FFDD00";
	}
	else{
		return "#00FF00";
	}
}

var svg = d3.select("body").append("svg").attr("width", width).attr("height",
		height).append("g").attr("transform",
		"translate(" + width / 2 + "," + height / 2 + ")");

function drawKaryo(karyo) {
	svg.append("g").selectAll("path").data(karyo).enter().append("path").style(
			"fill", function(d) {
				return fill(d.index);
			}).style("stroke", function(d) {
		return fill(d.index);
	})
			.attr(
					"d",
					d3.svg.arc().innerRadius(innerRadius).outerRadius(
							outerRadius)).on("mouseover", fade(.1)).on(
					"mouseout", fade(1));
	addTicks(karyo);
}

function addTicks(karyo) {
	var ticks = svg.append("g").selectAll("g").data(karyo).enter().append("g")
			.selectAll("g").data(groupTicks).enter().append("g").attr(
					"transform",
					function(d) {
						return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
								+ "translate(" + outerRadius + ",0)";
					});

	ticks.append("line").attr("x1", 1).attr("y1", 0).attr("x2", 5)
			.attr("y2", 0).style("stroke", "#000");

	ticks.append("text").attr("x", 8).attr("dy", ".35em").attr("transform",
			function(d) {
				return d.angle > Math.PI ? "rotate(180)translate(-16)" : null;
			}).style("text-anchor", function(d) {
		return d.angle > Math.PI ? "end" : null;
	}).text(function(d) {
		return d.label;
	});
}

function drawLinks(links) {
	svg.append("g").attr("class", "chord").selectAll("path").data(links)
			.enter().append("path").attr("d",
					d3.svg.chord().radius(innerRadius)).style("fill",
					function(d) {
					//	return fillByLength(Math.abs(d.target.end - d.target.start));
						return fillByIdy(Math.abs(d.identity));
					}).style("opacity", 1);
}

// Returns an array of tick angles and labels, given a group.
function groupTicks(d) {
	var k = (d.endAngle - d.startAngle) / d.value;
	return d3.range(0, d.value, 10000).map(function(v, i) {
		return {
			angle : v * k + d.startAngle,
			label : i % 5 ? null : v / 1000 + "k"
		};
	});
}

function loadKaryoFile(file, callback) {
	$.getJSON(file, function(data) {
		var karyo = karyo_to_coords(data);
	        if(typeof callback !== "undefined"){
		    callback(karyo);
		}
	});
}

function karyo_to_coords(data) {
	var total = 0;
	var spacer = 10000;
	$.each(data, function(key, value) {
		total += value + spacer;
	});
	var current = 0;
	var index = 0;
	$.each(data, function(key, value) {
		data[key] = {
			"value" : value,
			"startAngle" : ((current + spacer) / total) * (2 * Math.PI),
			"index" : index++
		};
		current += value + spacer;
		data[key].endAngle = (current / total) * (2 * Math.PI);
	});
	return data;
}

function loadLinkFile(file, karyo, callback) {
	$.getJSON(file, function(data) {
		var links = link_to_coords(data, karyo);
		if(typeof callback !== 'undefined'){
			callback(links);
		}
	});
}

function link_to_coords(links, karyo) {
	$.each(links, function(key, value) {
		var s = karyo[value.source.name];
		var s_totalAngle = s.endAngle - s.startAngle;
		links[key].source.startAngle = s.startAngle + s_totalAngle
				* (value.source.start / s.value);
		links[key].source.endAngle = s.startAngle + s_totalAngle
				* (value.source.end / s.value);
		links[key].source.index = s.index;
		links[key].source.value = Math.abs(value.source.end
				- value.source.start);
		var t = karyo[value.target.name];
		var t_totalAngle = t.endAngle - t.startAngle;
		links[key].target.endAngle = t.startAngle + t_totalAngle
				* (value.target.start / t.value);
		links[key].target.startAngle = t.startAngle + t_totalAngle
				* (value.target.end / t.value);
		links[key].target.index = t.index;
		links[key].target.value = Math.abs(value.target.end
				- value.target.start);
	});
	return links;
}

// Returns an event handler for fading a given chord group.
function fade(opacity) {
	return function(g, i) {
		svg.selectAll(".chord path").filter(function(d) {
			return d.source.index != i && d.target.index != i;
		}).transition().style("opacity", opacity);
	};
}
<commit_msg>added a clear_chords function to clear all links prior to redraw<commit_after>var width = 960,
	height = 800,
	innerRadius = Math.min(width, height) * .41,
	outerRadius = innerRadius * 1.1;

var fill = d3.scale.ordinal().domain(d3.range(5)).range(
		[ "#000000", "#FFDD89", "#957244", "#F26223", "#00FF00" ]);

function fillByLength(length){
	if(length < 10000){
		return "#FF0000";
	}
	else if(length < 50000){
		return "#FFDD00";
	}
	else{
		return "#00FF00";
	}
}

function fillByIdy(identity){
	if(identity < 90){
		return "#FF0000";
	}
	else if(identity < 95){
		return "#FFDD00";
	}
	else{
		return "#00FF00";
	}
}

var svg = d3.select("body").append("svg").attr("width", width).attr("height",
		height).append("g").attr("transform",
		"translate(" + width / 2 + "," + height / 2 + ")");

function drawKaryo(karyo) {
	svg.append("g").selectAll("path").data(karyo).enter().append("path").style(
			"fill", function(d) {
				return fill(d.index);
			}).style("stroke", function(d) {
		return fill(d.index);
	})
			.attr(
					"d",
					d3.svg.arc().innerRadius(innerRadius).outerRadius(
							outerRadius)).on("mouseover", fade(.1)).on(
					"mouseout", fade(1));
	addTicks(karyo);
}

function addTicks(karyo) {
	var ticks = svg.append("g").selectAll("g").data(karyo).enter().append("g")
			.selectAll("g").data(groupTicks).enter().append("g").attr(
					"transform",
					function(d) {
						return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
								+ "translate(" + outerRadius + ",0)";
					});

	ticks.append("line").attr("x1", 1).attr("y1", 0).attr("x2", 5)
			.attr("y2", 0).style("stroke", "#000");

	ticks.append("text").attr("x", 8).attr("dy", ".35em").attr("transform",
			function(d) {
				return d.angle > Math.PI ? "rotate(180)translate(-16)" : null;
			}).style("text-anchor", function(d) {
		return d.angle > Math.PI ? "end" : null;
	}).text(function(d) {
		return d.label;
	});
}

function drawLinks(links) {
	svg.append("g").attr("class", "chord").selectAll("path").data(links)
			.enter().append("path").attr("d",
					d3.svg.chord().radius(innerRadius)).style("fill",
					function(d) {
					//	return fillByLength(Math.abs(d.target.end - d.target.start));
						return fillByIdy(Math.abs(d.identity));
					}).style("opacity", 1);
}

// Returns an array of tick angles and labels, given a group.
function groupTicks(d) {
	var k = (d.endAngle - d.startAngle) / d.value;
	return d3.range(0, d.value, 10000).map(function(v, i) {
		return {
			angle : v * k + d.startAngle,
			label : i % 5 ? null : v / 1000 + "k"
		};
	});
}

function loadKaryoFile(file, callback) {
	$.getJSON(file, function(data) {
		var karyo = karyo_to_coords(data);
	        if(typeof callback !== "undefined"){
		    callback(karyo);
		}
	});
}

function karyo_to_coords(data) {
	var total = 0;
	var spacer = 10000;
	$.each(data, function(key, value) {
		total += value + spacer;
	});
	var current = 0;
	var index = 0;
	$.each(data, function(key, value) {
		data[key] = {
			"value" : value,
			"startAngle" : ((current + spacer) / total) * (2 * Math.PI),
			"index" : index++
		};
		current += value + spacer;
		data[key].endAngle = (current / total) * (2 * Math.PI);
	});
	return data;
}

function loadLinkFile(file, karyo, callback) {
	$.getJSON(file, function(data) {
		var links = link_to_coords(data, karyo);
		if(typeof callback !== 'undefined'){
			callback(links);
		}
	});
}

function link_to_coords(links, karyo) {
	$.each(links, function(key, value) {
		var s = karyo[value.source.name];
		var s_totalAngle = s.endAngle - s.startAngle;
		links[key].source.startAngle = s.startAngle + s_totalAngle
				* (value.source.start / s.value);
		links[key].source.endAngle = s.startAngle + s_totalAngle
				* (value.source.end / s.value);
		links[key].source.index = s.index;
		links[key].source.value = Math.abs(value.source.end
				- value.source.start);
		var t = karyo[value.target.name];
		var t_totalAngle = t.endAngle - t.startAngle;
		links[key].target.endAngle = t.startAngle + t_totalAngle
				* (value.target.start / t.value);
		links[key].target.startAngle = t.startAngle + t_totalAngle
				* (value.target.end / t.value);
		links[key].target.index = t.index;
		links[key].target.value = Math.abs(value.target.end
				- value.target.start);
	});
	return links;
}

// Returns an event handler for fading a given chord group.
function fade(opacity) {
	return function(g, i) {
		svg.selectAll(".chord path").filter(function(d) {
			return d.source.index != i && d.target.index != i;
		}).transition().style("opacity", opacity);
	};
}

function clear_chords(){
	svg.selectAll(".chord path").remove();
}
<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');

/**
 * 获取最终的配置数据
 * @param {Object | String} opts 用户传递过来的参数
 * @returns {Object}
 */
function getConfigOpts(opts) {
  let configOpts;

  try {
    // opts 如果是字符串则认为是文件路径，可将配置项放在独立的配置文件中
    if (typeof opts === 'string' && fs.existsSync(opts)) {
      configOpts = require(opts);
    } else if (opts && (typeof opts === 'object')) {
      configOpts = opts;
    } else {
      configOpts = null;
    }
  } catch (e) {
    console.error('getConfigOpts catch e:', e);
  }

  // 必须要存在配置
  if (!configOpts) {
    console.error('Could not get configOpts!', opts);
    return null;
  }

  // 如果没有 ROOT_PATH，则将无法启动成功
  if (!configOpts.ROOT_PATH) {
    console.error('Should define ROOT_PATH!', opts, configOpts);
    return null;
  }

  // 设置默认值
  // 项目源文件的目录，默认值为 ${ROOT_PATH}/src
  configOpts.SRC_PATH = configOpts.SRC_PATH || path.join(configOpts.ROOT_PATH, './src');

  // 运行目录，由 SRC_PATH 处理之后生成的，默认值为 ${ROOT_PATH}/app
  configOpts.APP_PATH = configOpts.APP_PATH || path.join(configOpts.ROOT_PATH, './app');

  // 配置数据缓存路径，默认值为 APP_PATH
  configOpts.DATA_PATH = configOpts.DATA_PATH || configOpts.APP_PATH;

  // mocker 的根目录
  configOpts.MOCKER_BASE_PATH = configOpts.MOCKER_BASE_PATH || './mocker';

  // handler 文件相对 SRC_PATH 目录的路径，默认值为 './handlers'
  configOpts.HANDLERS_RELATIVE_PATH = configOpts.HANDLERS_RELATIVE_PATH || './handlers';

  // 日志文件存储的路径，默认值为 ${ROOT_PATH}/logs
  configOpts.LOG_PATH = configOpts.LOG_PATH || path.join(configOpts.ROOT_PATH, 'logs');

  // matman 启动之后的服务端口号，默认为 3000
  configOpts.port = configOpts.port || 3000;

  // 外部 handler 列表，比如引入npm包或者其他目录下的 handler
  configOpts.definedHandlers = configOpts.definedHandlers || [];

  // 确认 HANDLERS_PATH 的值
  configOpts.HANDLERS_PATH = path.join(configOpts.APP_PATH, configOpts.MOCKER_BASE_PATH, configOpts.HANDLERS_RELATIVE_PATH);

  // 是否 watch 文件变动
  configOpts.shouldWatch = configOpts.shouldWatch || (process.env.NODE_ENV === 'development') || false;

  return configOpts;
}

module.exports = {
  getConfigOpts: getConfigOpts
};<commit_msg>refactor: 删除冗余代码<commit_after><|endoftext|>


<commit_before>
var recurs = require('../lib/recur');
var lists = require('../lib/list');

exports['Create recur'] = function (test) {
    var result = recurs.create([1, 2, 3]);

    test.ok(result);
    test.equal(result.length(), 3);
    test.equal(result.get(0), 1);
    test.equal(result.get(1), 2);
    test.equal(result.get(2), 3);

    test.deepEqual(result.items(), [1, 2, 3]);
}

exports['Is recur'] = function (test) {
    var result = recurs.create([1, 2, 3]);

    test.ok(result);
    test.ok(recurs.isRecur(result));
    test.ok(!recurs.isRecur(null));
    test.ok(!recurs.isRecur(false));
    test.ok(!recurs.isRecur(true));
    test.ok(!recurs.isRecur(42));
    test.ok(!recurs.isRecur("foo"));
    test.ok(!recurs.isRecur(lists.create([1, 2, 3])));
}

<commit_msg>Minor refactor in recur test<commit_after>
const recurs = require('../lib/recur');
const lists = require('../lib/list');

exports['Create recur'] = function (test) {
    const result = recurs.create([1, 2, 3]);

    test.ok(result);
    test.equal(result.length(), 3);
    test.equal(result.get(0), 1);
    test.equal(result.get(1), 2);
    test.equal(result.get(2), 3);

    test.deepEqual(result.items(), [1, 2, 3]);
}

exports['Is recur'] = function (test) {
    const result = recurs.create([1, 2, 3]);

    test.ok(result);
    test.ok(recurs.isRecur(result));
    test.ok(!recurs.isRecur(null));
    test.ok(!recurs.isRecur(false));
    test.ok(!recurs.isRecur(true));
    test.ok(!recurs.isRecur(42));
    test.ok(!recurs.isRecur("foo"));
    test.ok(!recurs.isRecur(lists.create([1, 2, 3])));
}

<|endoftext|>


<commit_before>/**
 * @class ArrayBuffer
 */

EZ3.ArrayBuffer = function() {
  this._id = null;
  this._index = {};
  this._vertex = {};
};

EZ3.ArrayBuffer.prototype.constructor = EZ3.ArrayBuffer;

EZ3.ArrayBuffer.prototype.bind = function(gl, attributes, state, index) {
  var buffer;
  var k;

  if (state.extension['OES_vertex_array_object']) {
    if (!this._id)
      this._id = state.extension['OES_vertex_array_object'].createVertexArrayOES();

    state.extension['OES_vertex_array_object'].bindVertexArrayOES(this._id);

    for (k in this._vertex) {
      buffer = this._vertex[k];

      if (buffer.validate(gl, attributes) && buffer.dirty) {
        buffer.bind(gl, attributes);
        buffer.update(gl);
        buffer.dirty = false;
      }
    }
  } else {
    for (k in this._vertex) {
      buffer = this._vertex[k];

      if (buffer.validate(gl, attributes)) {
        buffer.bind(gl, attributes, state);

        if (buffer.dirty) {
          buffer.update(gl);
          buffer.dirty = false;
        }
      }
    }
  }

  if (index) {
    index.bind(gl);

    if (index.dirty) {
      index.update(gl, state);
      index.dirty = false;
    }
  }
};

EZ3.ArrayBuffer.prototype.add = function(name, buffer) {
  if (buffer instanceof EZ3.IndexBuffer)
    this._index[name] = buffer;
  else if (buffer instanceof EZ3.VertexBuffer)
    this._vertex[name] = buffer;
};

EZ3.ArrayBuffer.prototype.get = function(name) {
  if (this._index[name])
    return this._index[name];
  else
    return this._vertex[name];
};
<commit_msg>Updated ArrayBuffer.js<commit_after>/**
 * @class ArrayBuffer
 */

EZ3.ArrayBuffer = function() {
  this._id = null;
  this._index = {};
  this._vertex = {};
};

EZ3.ArrayBuffer.prototype.constructor = EZ3.ArrayBuffer;

EZ3.ArrayBuffer.prototype.bind = function(gl, attributes, state, extension, index) {
  var buffer;
  var k;

  if (extension.vertexArrayObject) {
    if (!this._id)
      this._id = extension.vertexArrayObject.createVertexArrayOES();

    extension.vertexArrayObject.bindVertexArrayOES(this._id);

    for (k in this._vertex) {
      buffer = this._vertex[k];

      if (buffer.validate(gl, attributes) && buffer.dirty) {
        buffer.bind(gl, attributes);
        buffer.update(gl);
        buffer.dirty = false;
      }
    }
  } else {
    for (k in this._vertex) {
      buffer = this._vertex[k];

      if (buffer.validate(gl, attributes)) {
        buffer.bind(gl, attributes, state);

        if (buffer.dirty) {
          buffer.update(gl);
          buffer.dirty = false;
        }
      }
    }
  }

  if (index) {
    index.bind(gl);

    if (index.dirty) {
      index.update(gl, extension);
      index.dirty = false;
    }
  }
};

EZ3.ArrayBuffer.prototype.add = function(name, buffer) {
  if (buffer instanceof EZ3.IndexBuffer)
    this._index[name] = buffer;
  else if (buffer instanceof EZ3.VertexBuffer)
    this._vertex[name] = buffer;
};

EZ3.ArrayBuffer.prototype.get = function(name) {
  if (this._index[name])
    return this._index[name];
  else
    return this._vertex[name];
};
<|endoftext|>