diff --git a/.gitignore b/.gitignore index 2863ec0cb7..d815ed2081 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,6 @@ closed-lib # Node dependencies node_modules -# Build documentation -docs - # Protractor logs protractor/logs diff --git a/README.md b/README.md index c36cbea653..42cd060282 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ as described above. An example of this is expressed in `platform/framework`, which follows bundle conventions. -### Regression Testing +### Functional Testing The tests described above are all at the unit-level; an additional test suite using [Protractor](https://angular.github.io/protractor/) @@ -76,9 +76,9 @@ us under development, in the `protractor` folder. To run: * Install protractor following the instructions above. -* `webdriver-manager start` -* `node app.js -p 1984 -x platform/persistence/elastic -i example/persistence -* `protractor protractor/conf.js` +* `cd protractor` +* `npm install` +* `npm run all` ## Build diff --git a/build-docs.sh b/build-docs.sh index dd62c5ec41..c318a0dbda 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -24,7 +24,7 @@ # Script to build and deploy docs to github pages. -OUTPUT_DIRECTORY="docs" +OUTPUT_DIRECTORY="target/docs" REPOSITORY_URL="git@github.com:nasa/openmctweb.git" BUILD_SHA=`git rev-parse head` @@ -39,7 +39,7 @@ if [ -d $OUTPUT_DIRECTORY ]; then rm -rf $OUTPUT_DIRECTORY || exit 1 fi -npm run-script jsdoc +npm run docs cd $OUTPUT_DIRECTORY || exit 1 echo "git init" diff --git a/docs/gendocs.js b/docs/gendocs.js new file mode 100644 index 0000000000..2fcda7214e --- /dev/null +++ b/docs/gendocs.js @@ -0,0 +1,193 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/*global require,process,GLOBAL*/ +/*jslint nomen: false */ + + +// Usage: +// node gendocs.js --in --out + +var CONSTANTS = { + DIAGRAM_WIDTH: 800, + DIAGRAM_HEIGHT: 500 + }; + +GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be defined +(function () { + "use strict"; + + var fs = require("fs"), + mkdirp = require("mkdirp"), + path = require("path"), + glob = require("glob"), + marked = require("marked"), + split = require("split"), + stream = require("stream"), + nomnoml = require('nomnoml'), + Canvas = require('canvas'), + options = require("minimist")(process.argv.slice(2)); + + // Convert from nomnoml source to a target PNG file. + function renderNomnoml(source, target) { + var canvas = + new Canvas(CONSTANTS.DIAGRAM_WIDTH, CONSTANTS.DIAGRAM_HEIGHT); + nomnoml.draw(canvas, source, 1.0); + canvas.pngStream().pipe(fs.createWriteStream(target)); + } + + // Stream transform. + // Pulls out nomnoml diagrams from fenced code blocks and renders them + // as PNG files in the output directory, prefixed with a provided name. + // The fenced code blocks will be replaced with Markdown in the + // output of this stream. + function nomnomlifier(outputDirectory, prefix) { + var transform = new stream.Transform({ objectMode: true }), + isBuilding = false, + counter = 1, + outputPath, + source = ""; + + transform._transform = function (chunk, encoding, done) { + if (!isBuilding) { + if (chunk.trim().indexOf("```nomnoml") === 0) { + var outputFilename = prefix + '-' + counter + '.png'; + outputPath = path.join(outputDirectory, outputFilename); + this.push([ + "\n![Diagram ", + counter, + "](", + outputFilename, + ")\n\n" + ].join("")); + isBuilding = true; + source = ""; + counter += 1; + } else { + // Otherwise, pass through + this.push(chunk + '\n'); + } + } else { + if (chunk.trim() === "```") { + // End nomnoml + renderNomnoml(source, outputPath); + isBuilding = false; + } else { + source += chunk + '\n'; + } + } + done(); + }; + + return transform; + } + + // Convert from Github-flavored Markdown to HTML + function gfmifier() { + var transform = new stream.Transform({ objectMode: true }), + markdown = ""; + transform._transform = function (chunk, encoding, done) { + markdown += chunk; + done(); + }; + transform._flush = function (done) { + this.push("\n"); + this.push(marked(markdown)); + this.push("\n\n"); + done(); + }; + return transform; + } + + // Custom renderer for marked; converts relative links from md to html, + // and makes headings linkable. + function CustomRenderer() { + var renderer = new marked.Renderer(), + customRenderer = Object.create(renderer); + customRenderer.heading = function (text, level) { + var escapedText = (text || "").trim().toLowerCase().replace(/\W/g, "-"), + aOpen = "", + aClose = ""; + return aOpen + renderer.heading.apply(renderer, arguments) + aClose; + }; + // Change links to .md files to .html + customRenderer.link = function (href, title, text) { + // ...but only if they look like relative paths + return (href || "").indexOf(":") === -1 && href[0] !== "/" ? + renderer.link(href.replace(/\.md/, ".html"), title, text) : + renderer.link.apply(renderer, arguments); + }; + return customRenderer; + } + + options['in'] = options['in'] || options.i; + options.out = options.out || options.o; + + marked.setOptions({ + renderer: new CustomRenderer(), + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: true, + smartLists: true, + smartypants: false + }); + + // Convert all markdown files. + // First, pull out nomnoml diagrams. + // Then, convert remaining Markdown to HTML. + glob(options['in'] + "/**/*.md", {}, function (err, files) { + files.forEach(function (file) { + var destination = file.replace(options['in'], options.out) + .replace(/md$/, "html"), + destPath = path.dirname(destination), + prefix = path.basename(destination).replace(/\.html$/, ""); + + mkdirp(destPath, function (err) { + fs.createReadStream(file, { encoding: 'utf8' }) + .pipe(split()) + .pipe(nomnomlifier(destPath, prefix)) + .pipe(gfmifier()) + .pipe(fs.createWriteStream(destination, { + encoding: 'utf8' + })); + }); + }); + }); + + // Also copy over all HTML, CSS, or PNG files + glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) { + files.forEach(function (file) { + var destination = file.replace(options['in'], options.out), + destPath = path.dirname(destination); + + mkdirp(destPath, function (err) { + fs.createReadStream(file, { encoding: 'utf8' }) + .pipe(fs.createWriteStream(destination, { + encoding: 'utf8' + })); + }); + }); + }); + +}()); diff --git a/docs/src/architecture/Framework.md b/docs/src/architecture/Framework.md new file mode 100644 index 0000000000..229bee39c4 --- /dev/null +++ b/docs/src/architecture/Framework.md @@ -0,0 +1,232 @@ +# Overview + +The framework layer's most basic responsibility is allowing individual +software components to communicate. The software components it recognizes +are: + +* _Extensions_: Individual units of functionality that can be added to + or removed from Open MCT Web. _Extension categories_ distinguish what + type of functionality is being added/removed. +* _Bundles_: A grouping of related extensions + (named after an analogous concept from [OSGi](http://www.osgi.org/)) + that may be added or removed as a group. + +The framework layer operates by taking a set of active bundles, and +exposing extensions to one another as-needed, using +[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). +Extensions are responsible for declaring their dependencies in a +manner which the framework layer can understand. + +```nomnoml +#direction: down +[Open MCT Web| + [Dependency injection framework]-->[Platform bundle #1] + [Dependency injection framework]-->[Platform bundle #2] + [Dependency injection framework]-->[Plugin bundle #1] + [Dependency injection framework]-->[Plugin bundle #2] + [Platform bundle #1|[Extensions]] + [Platform bundle #2|[Extensions]] + [Plugin bundle #1|[Extensions]] + [Plugin bundle #2|[Extensions]] + [Platform bundle #1]<->[Platform bundle #2] + [Plugin bundle #1]<->[Platform bundle #2] + [Plugin bundle #1]<->[Plugin bundle #2] +] +``` + +The "dependency injection framework" in this case is +[AngularJS](https://angularjs.org/). Open MCT Web's framework layer +is really just a thin wrapper over Angular that recognizes the +concepts of bundles and extensions (as declared in JSON files) and +registering extensions with Angular. It additionally acts as a +mediator between Angular and [RequireJS](http://requirejs.org/), +which is used to load JavaScript sources which implement +extensions. + +```nomnoml +[Framework layer| + [AngularJS]<-[Framework Component] + [RequireJS]<-[Framework Component] + [Framework Component]1o-*[Bundles] +] +``` + +It is worth noting that _no other components_ are "aware" of the +framework component directly; Angular and Require are _used by_ the +framework components, and extensions in various bundles will have +their dependencies satisfied by Angular as a consequence of registration +activities which were performed by the framework component. + + +## Application Initialization + +The framework component initializes an Open MCT Web application following +a simple sequence of steps. + +```nomnoml +[ Start]->[ Load bundles.json] +[Load bundles.json]->[ Load bundle.json files] +[Load bundle.json files]->[ Resolve implementations] +[Resolve implementations]->[ Register with Angular] +[Register with Angular]->[ Bootstrap application] +[Bootstrap application]->[ End] +``` + +1. __Loading bundles.json.__ A file named `bundles.json` is loaded to determine + which bundles to load. Bundles are given in this file as relative paths + which point to bundle directories. +2. __Load bundle.json files.__ Individual bundle definitions are loaded; a + `bundle.json` file is expected in each bundle directory. +2. __Resolving implementations.__ Any scripts which provide implementations for + extensions exposed by bundles are loaded, using RequireJS. +3. __Register with Angular.__ Resolved extensions are registered with Angular, + such that they can be used by the application at run-time. This stage + includes both registration of Angular built-ins (directives, controllers, + routes, constants, and services) as well as registration of non-Angular + extensions. +4. __Bootstrap application.__ Once all extensions have been registered, + the Angular application + [is bootstrapped](https://docs.angularjs.org/guide/bootstrap). + +## Architectural Paradigm + +```nomnoml +[Extension] +[Extension]o->[Dependency #1] +[Extension]o->[Dependency #2] +[Extension]o->[Dependency #3] +``` + +Open MCT Web's architecture relies on a simple premise: Individual units +(extensions) only have access to the dependencies they declare that they +need, and they acquire references to these dependencies via dependency +injection. This has several desirable traits: + +* Programming to an interface is enforced. Any given dependency can be + swapped out for something which exposes an equivalent interface. This + improves flexibility against refactoring, simplifies testing, and + provides a common mechanism for extension and reconfiguration. +* The dependencies of a unit must be explicitly defined. This means that + it can be easily determined what a given unit's role is within the + larger system, in terms of what other components it will interact with. + It also helps to enforce good separation of concerns: When a set of + declared dependencies becomes long it is obvious, and this is usually + a sign that a given unit is involved in too many concerns and should + be refactored into smaller pieces. +* Individual units do not need to be aware of the framework; they need + only be aware of the interfaces to the components they specifically + use. This avoids introducing a ubiquitous dependency upon the framework + layer itself; it is plausible to modify or replace the framework + without making changes to individual software components which run upon + the framework. + +A drawback to this approach is that it makes it difficult to define +"the architecture" of Open MCT Web, in terms of describing the specific +units that interact at run-time. The run-time architecture is determined +by the framework as the consequence of wiring together dependencies. +As such, the specific architecture of any given application built on +Open MCT Web can look very different. + +Keeping that in mind, there are a few useful patterns supported by the +framework that are useful to keep in mind. + +The specific service infrastructure provided by the platform is described +in the [Platform Architecture](Platform.md). + +## Extension Categories + +One of the capabilities that the framework component layers on top of +AngularJS is support for many-to-one dependencies. That is, a specific +extension may declare a dependency to _all extensions of a specific +category_, instead of being limited to declaring specific dependencies. + +```nomnoml +#direction: right +[Specific Extension] 1 o-> * [Extension of Some Category] +``` + +This is useful for introducing specific extension points to an application. +Some unit of software will depend upon all extensions of a given category +and integrate their behavior into the system in some fashion; plugin authors +can then add new extensions of that category to augment existing behaviors. + +Some developers may be familiar with the use of registries to achieve +similar characteristics. This approach is similar, except that the registry +is effectively implicit whenever a new extension category is used or +depended-upon. This has some advantages over a more straightforward +registry-based approach: + +* These many-to-one relationships are expressed as dependencies; an + extension category is registered as having dependencies on all individual + extensions of this category. This avoids ordering issues that may occur + with more conventional registries, which may be observed before all + dependencies are resolved. +* The need for service registries of specific types is removed, reducing + the number of interfaces to manage within the system. Groups of + extensions are provided as arrays. + +## Composite Services + +Composite services (registered via extension category `components`) are +a pattern supported by the framework. These allow service instances to +be built from multiple components at run-time; support for this pattern +allows additional bundles to introduce or modify behavior associated +with these services without modifying or replacing original service +instances. + +```nomnoml +#direction: down +[ FooService] +[FooDecorator #1]--:>[FooService] +[FooDecorator #n]--:>[FooService] +[FooAggregator]--:>[FooService] +[FooProvider #1]--:>[FooService] +[FooProvider #n]--:>[FooService] + +[FooDecorator #1]o->[ ...decorators...] +[...decorators...]o->[FooDecorator #n] +[FooDecorator #n]o->[FooAggregator] +[FooAggregator]o->[FooProvider #1] +[FooAggregator]o->[ ...providers...] +[FooAggregator]o->[FooProvider #n] + +[FooDecorator #1]--[ Exposed as fooService] +``` + +In this pattern, components all implement an interface which is +standardized for that service. Components additionally declare +that they belong to one of three types: + +* __Providers.__ A provider actually implements the behavior + (satisfies the contract) for that kind of service. For instance, + if a service is responsible for looking up documents by an identifier, + one provider may do so by querying a database, while another may + do so by reading a static JSON document. From the outside, either + provider would look the same (they expose the same interface) and + they could be swapped out easily. +* __Aggregator.__ An aggregator takes many providers and makes them + behave as one. Again, this implements the same interface as an + individual provider, so users of the service do not need to be + concerned about the difference between consulting many providers + and consulting one. Continuing with the example of a service that + looks up documents by identifiers, an aggregator here might consult + all providers, and return any document is found (perhaps picking one + over the other or merging documents if there are multiple matches.) +* __Decorators.__ A decorator exposes the same interface as other + components, but instead of fully implementing the behavior associated + with that kind of service, it only acts as an intermediary, delegating + the actual behavior to a different component. Decorators may transform + inputs or outputs, or initiate some side effects associated with a + service. This is useful if certain common behavior associated with a + service (caching, for instance) may be useful across many different + implementations of that same service. + +The framework will register extensions in this category such that an +aggregator will depend on all of its providers, and decorators will +depend upon on one another in a chain. The result of this compositing step +(the last decorator, if any; otherwise the aggregator, if any; +otherwise a single provider) will be exposed as a single service that +other extensions can acquire through dependency injection. Because all +components of the same type of service expose the same interface, users +of that service do not need to be aware that they are talking to an +aggregator or a provider, for instance. \ No newline at end of file diff --git a/docs/src/architecture/Platform.md b/docs/src/architecture/Platform.md new file mode 100644 index 0000000000..80f9e487f5 --- /dev/null +++ b/docs/src/architecture/Platform.md @@ -0,0 +1,714 @@ +# Overview + +The Open MCT Web platform utilizes the [framework layer](Framework.md) +to provide an extensible baseline for applications which includes: + +* A common user interface (and user interface paradigm) for dealing with + domain objects of various sorts. +* A variety of extension points for introducing new functionality + of various kinds within the context of the common user interface. +* A service infrastructure to support building additional components. + +## Platform Architecture + +While the framework provides a more general architectural paradigm for +building application, the platform adds more specificity by defining +additional extension types and allowing for integration with back end +components. + +The run-time architecture of an Open MCT Web application can be categorized +into certain high-level tiers: + +```nomnoml +[DOM]->[ AngularJS] +[AngularJS]->[Presentation Layer] +[Presentation Layer]->[Information Model] +[Presentation Layer]->[Service Infrastructure] +[Information Model]->[Service Infrastructure] +[Service Infrastructure]->[ Browser APIs] +[Browser APIs]->[Back-end] +``` + +Applications built using Open MCT Web may add or configure functionality +in __any of these tiers__. + +* _DOM_: The rendered HTML document, composed from HTML templates which + have been processed by AngularJS and will be updated by AngularJS + to reflect changes from the presentation layer. User interactions + are initiated from here and invoke behavior in the presentation layer. +* [_Presentation layer_](#presentation-layer): The presentation layer + is responsible for updating (and providing information to update) + the displayed state of the application. The presentation layer consists + primarily of _controllers_ and _directives_. The presentation layer is + concerned with inspecting the information model and preparing it for + display. +* [_Information model_](#information-model): The information model + describes the state and behavior of the objects with which the user + interacts. +* [_Service infrastructure_](#service-infrastructure): The service + infrastructure is responsible for providing the underlying general + functionality needed to support the information model. This includes + exposing underlying sets of extensions and mediating with the + back-end. +* _Back-end_: The back-end is out of the scope of Open MCT Web, except + for the interfaces which are utilized by adapters participating in the + service infrastructure. + +## Application Start-up + +Once the +[application has been initialized](Framework.md#application-initialization) +Open MCT Web primarily operates in an event-driven paradigm; various +events (mouse clicks, timers firing, receiving responses to XHRs) trigger +the invocation of functions, typically in the presentation layer for +user actions or in the service infrastructure for server responses. + +The "main point of entry" into an initialized Open MCT Web application +is effectively the +[route](https://docs.angularjs.org/api/ngRoute/service/$route#example) +which is associated with the URL used to access Open MCT Web (or a +default route.) This route will be associated with a template which +will be displayed; this template will include references to directives +and controllers which will be interpreted by Angular and used to +initialize the state of the display in a manner which is backed by +both the information model and the service infrastructure. + +```nomnoml +[ Start]->[ page load] +[page load]->[ route selection] +[route selection]->[ compile, display template] +[compile, display template]->[Template] +[Template]->[ use Controllers] +[Template]->[ use Directives] +[use Controllers]->[Controllers] +[use Directives]->[Directives] +[Controllers]->[ consult information model] +[consult information model]->[ expose data] +[expose data]->[Angular] +[Angular]->[ update display] +[Directives]->[ add event listeners] +[Directives]->[ update display] +[add event listeners]->[ End] +[update display]->[ End] +``` + + +# Presentation Layer + +The presentation layer of Open MCT Web is responsible for providing +information to display within templates, and for handling interactions +which are initiated from templated DOM elements. AngularJS acts as +an intermediary between the web page as the user sees it, and the +presentation layer implemented as Open MCT Web extensions. + +```nomnoml +[Presentation Layer| + [Angular built-ins| + [routes] + [controllers] + [directives] + [templates] + ] + [Domain object representation| + [views] + [representations] + [representers] + [gestures] + ] +] +``` + +## Angular built-ins + +Several extension categories in the presentation layer map directly +to primitives from AngularJS: + +* [_Controllers_](https://docs.angularjs.org/guide/controller) provide + data to templates, and expose functionality that can be called from + templates. +* [_Directives_](https://docs.angularjs.org/guide/directive) effectively + extend HTML to provide custom behavior associated with specific + attributes and tags. +* [_Routes_](https://docs.angularjs.org/api/ngRoute/service/$route#example) + are used to associate specific URLs (including the fragment identifier) + with specific application states. (In Open MCT Web, these are used to + describe the mode of usage - e.g. browse or edit - as well as to + identify the object being used.) +* [_Templates_](https://docs.angularjs.org/guide/templates) are partial + HTML documents that will be rendered and kept up-to-date by AngularJS. + Open MCT Web introduces a custom `mct-include` directive which acts + as a wrapper around `ng-include` to allow templates to be referred + to by symbolic names. + +## Domain object representation + +The remaining extension categories in the presentation layer are specific +to displaying domain objects. + +* _Representations_ are templates that will be used to display + domain objects in specific ways (e.g. "as a tree node.") +* _Views_ are representations which are exposed to the user as options + for displaying domain objects. +* _Representers_ are extensions which modify or augment the process + of representing domain objects generally (e.g. by attaching + gestures to them.) +* _Gestures_ provide associations between specific user actions + (expressed as DOM events) and resulting behavior upon domain objects + (typically expressed as members of the `actions` extension category) + that can be reused across domain objects. For instance, `drag` and + `drop` are both gestures associated with using drag-and-drop to + modify the composition of domain objects by interacting with their + representations. + +# Information Model + +```nomnoml +#direction: right +[Information Model| + [DomainObject| + getId() : string + getModel() : object + getCapability(key : string) : Capability + hasCapability(key : string) : boolean + useCapability(key : string, args...) : * + ] + [DomainObject] 1 +- 1 [Model] + [DomainObject] 1 o- * [Capability] +] +``` + +Domain objects are the most fundamental component of Open MCT Web's +information model. A domain object is some distinct thing relevant to a +user's work flow, such as a telemetry channel, display, or similar. +Open MCT Web is a tool for viewing, browsing, manipulating, and otherwise +interacting with a graph of domain objects. + +A domain object should be conceived of as the union of the following: + +* _Identifier_: A machine-readable string that uniquely identifies the + domain object within this application instance. +* _Model_: The persistent state of the domain object. A domain object's + model is a JavaScript object that can be losslessly converted to JSON. +* _Capabilities_: Dynamic behavior associated with the domain object. + Capabilities are JavaScript objects which provide additional methods + for interacting with the domain objects which expose those capabilities. + Not all domain objects expose all capabilities. The interface exposed + by any given capability will depend on its type (as identified + by the `key` argument.) For instance, a `persistence` capability + has a different interface from a `telemetry` capability. Using + capabilities requires some prior knowledge of their interface. + +## Capabilities and Services + +```nomnoml +#direction: right +[DomainObject]o-[FooCapability] +[FooCapability]o-[FooService] +[FooService]o-[foos] +``` + +At run-time, the user is primarily concerned with interacting with +domain objects. These interactions are ultimately supported via back-end +services, but to allow customization per-object, these are often mediated +by capabilities. + +A common pattern that emerges in the Open MCT Platform is as follows: + +* A `DomainObject` has some particular behavior that will be supported + by a service. +* A `Capability` of that domain object will define that behavior, + _for that domain object_, supported by a service. +* A `Service` utilized by that capability will perform the actual behavior. +* An extension category will be utilized by that capability to determine + the set of possible behaviors. + +Concrete examples of capabilities which follow this pattern +(or a subset of this pattern) include: + +```nomnoml +#direction: right +[DomainObject]1 o- *[Capability] +[Capability]<:--[TypeCapability] +[Capability]<:--[ActionCapability] +[Capability]<:--[PersistenceCapability] +[Capability]<:--[TelemetryCapability] +[TypeCapability]o-[TypeService] +[TypeService]o-[types] +[ActionCapability]o-[ActionService] +[ActionService]o-[actions] +[PersistenceCapability]o-[PersistenceService] +[TelemetryCapability]o-[TelemetryService] +``` + +# Service Infrastructure + +Most services exposed by the Open MCT Web platform follow the +[composite services](Framework.md#composite-services) to permit +a higher degree of flexibility in how a service can be modified +or customized for specific applications. + +To simplify usage for plugin developers, the platform also usually +includes a provider implementation for these service type that consumes +some extension category. For instance, an `ActionService` provider is +included which depends upon extension category `actions`, and exposes +all actions declared as such to the system. As such, plugin developers +can simply implement the new actions they wish to be made available without +worrying about the details of composite services or implementing a new +`ActionService` provider; however, the ability to implement a new provider +remains useful when the expressive power of individual extensions is +insufficient. + +```nomnoml +[ Service Infrastructure | + [ObjectService]->[ModelService] + [ModelService]->[PersistenceService] + [ObjectService]->[CapabilityService] + [CapabilityService]->[capabilities] + [capabilities]->[TelemetryService] + [capabilities]->[PersistenceService] + [capabilities]->[TypeService] + [capabilities]->[ActionService] + [capabilities]->[ViewService] + [PersistenceService]->[ Document store] + [TelemetryService]->[ Telemetry source] + [ActionService]->[actions] + [ActionService]->[PolicyService] + [ViewService]->[PolicyService] + [ViewService]->[views] + [PolicyService]->[policies] + [TypeService]->[types] +] +``` + +A short summary of the roles of these services: + +* _[ObjectService](#object-service)_: Allows retrieval of domain objects by + their identifiers; in practice, often the main point of entry into the + [information model](#information-model). +* _[ModelService](#model-service)_: Provides domain object models, retrieved + by their identifier. +* _[CapabilityService](#capability-service)_: Provides capabilities, as they + apply to specific domain objects (as judged from their model.) +* _[TelemetryService](#telemetry-service)_: Provides access to historical + and real-time telemetry data. +* _[PersistenceService](#persistence-service)_: Provides the ability to + store and retrieve documents (such as domain object models.) +* _[ActionService](#action-service)_: Provides distinct user actions that + can take place within the system (typically, upon or using domain objects.) +* _[ViewService](#view-service)_: Provides views for domain objects. A view + is a user-selectable representation of a domain object (in practice, an + HTML template.) +* _[PolicyService](#policy-service)_: Handles decisions about which + behavior are allowed within certain specific contexts. +* _[TypeService](#type-service)_: Provides information to distinguish + different types of domain objects from one another within the system. + +## Object Service + +```nomnoml +#direction: right +[ ObjectService| + getObjects(ids : Array.) : Promise.> +] +[DomainObjectProvider]--:>[ObjectService] +[DomainObjectProvider]o-[ModelService] +[DomainObjectProvider]o-[CapabilityService] +``` + +As domain objects are central to Open MCT Web's information model, +acquiring domain objects is equally important. + +```nomnoml +#direction: right +[ Start]->[ Look up models] +[ Look up models]->[ Look up capabilities] +[ Look up capabilities]->[ Instantiate DomainObject] +[ Instantiate DomainObject]->[ End] +``` + +Open MCT Web includes an implementation of an `ObjectService` which +satisfies this capability by: + +* Consulting the [Model Service](#model-service) to acquire domain object + models by identifier. +* Passing these models to a [Capability Service](#capability-service) to + determine which capabilities are applicable. +* Combining these results together as [DomainObject](#information-model) + instances. + +## Model Service + +```nomnoml +#direction: down +[ ModelService| + getModels(ids : Array.) : Promise.> +] +[StaticModelProvider]--:>[ModelService] +[RootModelProvider]--:>[ModelService] +[PersistedModelProvider]--:>[ModelService] +[ModelAggregator]--:>[ModelService] +[CachingModelDecorator]--:>[ModelService] +[MissingModelDecorator]--:>[ModelService] + +[MissingModelDecorator]o-[CachingModelDecorator] +[CachingModelDecorator]o-[ModelAggregator] +[ModelAggregator]o-[StaticModelProvider] +[ModelAggregator]o-[RootModelProvider] +[ModelAggregator]o-[PersistedModelProvider] + +[PersistedModelProvider]o-[PersistenceService] +[RootModelProvider]o-[roots] +[StaticModelProvider]o-[models] +``` + +The platform's model service is responsible for providing domain object +models (effectively, JSON documents describing the persistent state +associated with domain objects.) These are retrieved by identifier. + +The platform includes multiple components of this variety: + +* `PersistedModelProvider` looks up domain object models from + a persistence store (the [`PersistenceService`](#persistence-service)); + this is how user-created and user-modified + domain object models are retrieved. +* `RootModelProvider` provides domain object models that have been + declared via the `roots` extension category. These will appear at the + top level of the tree hierarchy in the user interface. +* `StaticModelProvider` provides domain object models that have been + declared via the `models` extension category. This is useful for + allowing plugins to expose new domain objects declaratively. +* `ModelAggregator` merges together the results from multiple providers. + If multiple providers return models for the same domain object, + the most recently modified version (as determined by the `modified` + property of the model) is chosen. +* `CachingModelDecorator` caches model instances in memory. This + ensures that only a single instance of a domain object model is + present at any given time within the application, and prevent + redundant retrievals. +* `MissingModelDecorator` adds in placeholders when no providers + have returned domain object models for a specific identifier. This + allows the user to easily see that something was expected to be + present, but wasn't. + +## Capability Service + +```nomnoml +#direction: down +[ CapabilityService| + getCapabilities(model : object) : object. +] +[CoreCapabilityProvider]--:>[CapabilityService] +[QueuingPersistenceCapabilityDecorator]--:>[CapabilityService] + +[CoreCapabilityProvider]o-[capabilities] +[QueuingPersistenceCapabilityDecorator]o-[CoreCapabilityProvider] +``` + +The capability service is responsible for determining which capabilities +are applicable for a given domain object, based on its model. Primarily, +this is handled by the `CoreCapabilityProvider`, which examines +capabilities exposed via the `capabilities` extension category. + +Additionally, `platform/persistence/queue` decorates the persistence +capability specifically to batch persistence attempts among multiple +objects (this allows failures to be recognized and handled in groups.) + +## Telemetry Service + +```nomnoml +[ TelemetryService| + requestData(requests : Array.) : Promise. + subscribe(requests : Array.) : Function +]<--:[TelemetryAggregator] +``` + +The telemetry service is responsible for acquiring telemetry data. + +Notably, the platform does not include any providers for +`TelemetryService`; applications built on Open MCT Web will need to +implement a provider for this service if they wish to expose telemetry +data. This is usually the most important step for integrating Open MCT Web +into an existing telemetry system. + +Requests for telemetry data are usually initiated in the +[presentation layer](#presentation-layer) by some `Controller` referenced +from a view. The `telemetryHandler` service is most commonly used (although +one could also use an object's `telemetry` capability directly) as this +handles capability delegation, by which a domain object such as a Telemetry +Panel can declare that its `telemetry` capability should be handled by the +objects it contains. Ultimately, the request for historical data and the +new subscriptions will reach the `TelemetryService`, and, by way of the +provider(s) which are present for that `TelemetryService`, will pass the +same requests to the back-end. + +```nomnoml +[ Start]->[Controller] +[Controller]->[ declares object of interest] +[declares object of interest]->[TelemetryHandler] +[TelemetryHandler]->[ requests telemetry from capabilities] +[TelemetryHandler]->[ subscribes to telemetry using capabilities] +[requests telemetry from capabilities]->[TelemetryCapability] +[subscribes to telemetry using capabilities]->[TelemetryCapability] +[TelemetryCapability]->[ requests telemetry] +[TelemetryCapability]->[ subscribes to telemetry] +[requests telemetry]->[TelemetryService] +[subscribes to telemetry]->[TelemetryService] +[TelemetryService]->[ issues request] +[TelemetryService]->[ updates subscriptions] +[TelemetryService]->[ listens for real-time data] +[issues request]->[ Telemetry Back-end] +[updates subscriptions]->[Telemetry Back-end] +[listens for real-time data]->[Telemetry Back-end] +[Telemetry Back-end]->[ End] +``` + +The back-end, in turn, is expected to provide whatever historical +telemetry is available to satisfy the request that has been issue. + +```nomnoml +[ Start]->[ Telemetry Back-end] +[Telemetry Back-end]->[ transmits historical telemetry] +[transmits historical telemetry]->[TelemetryService] +[TelemetryService]->[ packages telemetry, fulfills requests] +[packages telemetry, fulfills requests]->[TelemetryCapability] +[TelemetryCapability]->[ unpacks telemetry per-object, fulfills request] +[unpacks telemetry per-object, fulfills request]->[TelemetryHandler] +[TelemetryHandler]->[ exposes data] +[TelemetryHandler]->[ notifies controller] +[exposes data]->[Controller] +[notifies controller]->[Controller] +[Controller]->[ prepares data for template] +[prepares data for template]->[Template] +[Template]->[ displays data] +[displays data]->[ End] +``` + +One peculiarity of this approach is that we package many responses +together at once in the `TelemetryService`, then unpack these in the +`TelemetryCapability`, then repackage these in the `TelemetryHandler`. +The rationale for this is as follows: + +* In the `TelemetryService`, we want to have the ability to combine + multiple requests into one call to the back-end, as many back-ends + will support this. It follows that we give the response as a single + object, packages in a manner that allows responses to individual + requests to be easily identified. +* In the `TelemetryCapability`, we want to provide telemetry for a + _single object_, so the telemetry data gets unpacked. This allows + for the unpacking of data to be handled in a single place, and + also permits a flexible substitution method; domain objects may have + implementations of the `telemetry` capability that do not use the + `TelemetryService` at all, while still maintaining compatibility + with any presentation layer code written to utilize this capability. + (This is true of capabilities generally.) +* In the `TelemetryHandler`, we want to group multiple responses back + together again to make it easy for the presentation layer to consume. + In this case, the grouping is different from what may have occurred + in the `TelemetryService`; this grouping is based on what is expected + to be useful _in a specific view_. The `TelemetryService` + may be receiving requests from multiple views. + +```nomnoml +[ Start]->[ Telemetry Back-end] +[Telemetry Back-end]->[ notifies client of new data] +[notifies client of new data]->[TelemetryService] +[TelemetryService]->[ relevant subscribers?] +[relevant subscribers?] yes ->[ notify subscribers] +[relevant subscribers?] no ->[ ignore] +[ignore]->[ Ignored] +[notify subscribers]->[TelemetryCapability] +[TelemetryCapability]->[ notify listener] +[notify listener]->[TelemetryHandler] +[TelemetryHandler]->[ exposes data] +[TelemetryHandler]->[ notifies controller] +[exposes data]->[Controller] +[notifies controller]->[Controller] +[Controller]->[ prepares data for template] +[prepares data for template]->[Template] +[Template]->[ displays data] +[displays data]->[ End] +``` + +The flow of real-time data is similar, and is handled by a sequence +of callbacks between the presentation layer component which is +interested in data and the telemetry service. Providers in the +telemetry service listen to the back-end for new data (via whatever +mechanism their specific back-end supports), package this data in +the same manner as historical data, and pass that to the callbacks +which are associated with relevant requests. + +## Persistence Service + +```nomnoml +#direction: right +[ PersistenceService| + listSpaces() : Promise.> + listObjects() : Promise.> + createObject(space : string, key : string, document : object) : Promise. + readObject(space : string, key : string, document : object) : Promise. + updateObject(space : string, key : string, document : object) : Promise. + deleteObject(space : string, key : string, document : object) : Promise. +] + +[ElasticPersistenceProvider]--:>[PersistenceService] +[ElasticPersistenceProvider]->[ ElasticSearch] + +[CouchPersistenceProvider]--:>[PersistenceService] +[CouchPersistenceProvider]->[ CouchDB] +``` + +Closely related to the notion of domain objects models is their +persistence. The `PersistenceService` allows these to be saved +and loaded. (Currently, this capability is only used for domain +object models, but the interface has been designed without this idea +in mind; other kinds of documents could be saved and loaded in the +same manner.) + +There is no single definitive implementation of a `PersistenceService` in +the platform. Optional adapters are provided to store and load documents +from CouchDB and ElasticSearch, respectively; plugin authors may also +write additional adapters to utilize different back end technologies. + +## Action Service + +```nomnoml +[ActionService| + getActions(context : ActionContext) : Array. +] +[ActionProvider]--:>[ActionService] +[CreateActionProvider]--:>[ActionService] +[ActionAggregator]--:>[ActionService] +[LoggingActionDecorator]--:>[ActionService] +[PolicyActionDecorator]--:>[ActionService] + +[LoggingActionDecorator]o-[PolicyActionDecorator] +[PolicyActionDecorator]o-[ActionAggregator] +[ActionAggregator]o-[ActionProvider] +[ActionAggregator]o-[CreateActionProvider] + +[ActionProvider]o-[actions] +[CreateActionProvider]o-[TypeService] +[PolicyActionDecorator]o-[PolicyService] +``` + +Actions are discrete tasks or behaviors that can be initiated by a user +upon or using a domain object. Actions may appear as menu items or +buttons in the user interface, or may be triggered by certain gestures. + +Responsibilities of platform components of the action service are as +follows: + +* `ActionProvider` exposes actions registered via extension category + `actions`, supporting simple addition of new actions. Actions are + filtered down to match action contexts based on criteria defined as + part of an action's extension definition. +* `CreateActionProvider` provides the various Create actions which + populate the Create menu. These are driven by the available types, + so do not map easily ot extension category `actions`; instead, these + are generated after looking up which actions are available from the + [`TypeService`](#type-service). +* `ActionAggregator` merges together actions from multiple providers. +* `PolicyActionDecorator` enforces the `action` policy category by + filtering out actions which violate this policy, as determined by + consulting the [`PolicyService`](#policy-service). +* `LoggingActionDecorator` wraps exposed actions and writes to the + console when they are performed. + +## View Service + +```nomnoml +[ViewService| + getViews(domainObject : DomainObject) : Array. +] +[ViewProvider]--:>[ViewService] +[PolicyViewDecorator]--:>[ViewService] + +[ViewProvider]o-[views] +[PolicyViewDecorator]o-[ViewProvider] +``` + +The view service provides views that are relevant to a specified domain +object. A "view" is a user-selectable visualization of a domain object. + +The responsibilities of components of the view service are as follows: + +* `ViewProvider` exposes views registered via extension category + `views`, supporting simple addition of new views. Views are + filtered down to match domain objects based on criteria defined as + part of a view's extension definition. +* `PolicyViewDecorator` enforces the `view` policy category by + filtering out views which violate this policy, as determined by + consulting the [`PolicyService`](#policy-service). + +## Policy Service + +```nomnoml +[PolicyService| + allow(category : string, candidate : object, context : object, callback? : Function) : boolean +] +[PolicyProvider]--:>[PolicyService] +[PolicyProvider]o-[policies] +``` + +The policy service provides a general-purpose extensible decision-making +mechanism; plugins can add new extensions of category `policies` to +modify decisions of a known category. + +Often, the policy service is referenced from a decorator for another +service, to filter down the results of using that service based on some +appropriate policy category. + +The policy provider works by looking up all registered policy extensions +which are relevant to a particular _category_, then consulting each in +order to see if they allow a particular _candidate_ in a particular +_context_; the types for the `candidate` and `context` arguments will +vary depending on the `category`. Any one policy may disallow the +decision as a whole. + + +```nomnoml +[ Start]->[ is something allowed?] +[is something allowed?]->[PolicyService] +[PolicyService]->[ look up relevant policies by category] +[look up relevant policies by category]->[ consult policy #1] +[consult policy #1]->[Policy #1] +[Policy #1]->[ policy #1 allows?] +[policy #1 allows?] no ->[ decision disallowed] +[policy #1 allows?] yes ->[ consult policy #2] +[consult policy #2]->[Policy #2] +[Policy #2]->[ policy #2 allows?] +[policy #2 allows?] no ->[ decision disallowed] +[policy #2 allows?] yes ->[ consult policy #3] +[consult policy #3]->[ ...] +[...]->[ consult policy #n] +[consult policy #n]->[Policy #n] +[Policy #n]->[ policy #n allows?] +[policy #n allows?] no ->[ decision disallowed] +[policy #n allows?] yes ->[ decision allowed] +[decision disallowed]->[ Disallowed] +[decision allowed]->[ Allowed] +``` + +The policy decision is effectively an "and" operation over the individual +policy decisions: That is, all policies must agree to allow a particular +policy decision, and the first policy to disallow a decision will cause +the entire decision to be disallowed. As a consequence of this, policies +should generally be written with a default behavior of "allow", and +should only disallow the specific circumstances they are intended to +disallow. + +## Type Service + +```nomnoml +[TypeService| + listTypes() : Array. + getType(key : string) : Type +] +[TypeProvider]--:>[TypeService] +[TypeProvider]o-[types] +``` + +The type service provides metadata about the different types of domain +objects that exist within an Open MCT Web application. The platform +implementation reads these types in from extension category `types` +and wraps them in a JavaScript interface. \ No newline at end of file diff --git a/docs/src/architecture/index.md b/docs/src/architecture/index.md new file mode 100644 index 0000000000..fd9e0961e1 --- /dev/null +++ b/docs/src/architecture/index.md @@ -0,0 +1,78 @@ +# Introduction + +The purpose of this document is to familiarize developers with the +overall architecture of Open MCT Web. + +The target audience includes: + +* _Platform maintainers_: Individuals involved in developing, + extending, and maintaing capabilities of the platform. +* _Integration developers_: Individuals tasked with integrated + Open MCT Web into a larger system, who need to understand + its inner workings sufficiently to complete this integration. + +As the focus of this document is on architecture, whenever possible +implementation details (such as relevant API or JSON syntax) have been +omitted. These details may be found in the developer guide. + +# Overview + +Open MCT Web is client software: It runs in a web browser and +provides a user interface, while communicating with various +server-side resources through browser APIs. + +```nomnoml +#direction: right +[Client|[Browser|[Open MCT Web]->[Browser APIs]]] +[Server|[Web services]] +[Client]<->[Server] +``` + +While Open MCT Web can be configured to run as a standalone client, +this is rarely very useful. Instead, it is intended to be used as a +display and interaction layer for information obtained from a +variety of back-end services. Doing so requires authoring or utilizing +adapter plugins which allow Open MCT Web to interact with these services. + +Typically, the pattern here is to provide a known interface that +Open MCT Web can utilize, and implement it such that it interacts with +whatever back-end provides the relevant information. +Examples of back-ends that can be utilized in this fashion include +databases for the persistence of user-created objects, or sources of +telemetry data. + +## Software Architecture + +The simplest overview of Open MCT Web is to look at it as a "layered" +architecture, where each layer more clearly specifies the behavior +of the software. + +```nomnoml +#direction: down +[Open MCT Web| + [Platform]<->[Application] + [Framework]->[Application] + [Framework]->[Platform] +] +``` + +These layers are: + +* [_Framework_](Framework.md): The framework layer is responsible for + managing the interactions between application components. It has no + application-specific knowledge; at this layer, we have only + established an abstraction by which different software components + may communicate and/or interact. +* [_Platform_](Platform.md): The platform layer defines the general look, feel, and + behavior of Open MCT Web. This includes user-facing components like + Browse mode and Edit mode, as well as underlying elements of the + information model and the general service infrastructure. +* _Application_: The application layer defines specific features of + an application built on Open MCT Web. This includes adapters to + specific back-ends, new types of things for users to create, and + new ways of visualizing objects within the system. This layer + typically consists of a mix of custom plug-ins to Open MCT Web, + as well as optional features (such as Plot view) included alongside + the platform. + + diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md new file mode 100644 index 0000000000..c575439d48 --- /dev/null +++ b/docs/src/guide/index.md @@ -0,0 +1,3 @@ +# Developer Guide + +This is a placeholder for the developer guide. diff --git a/docs/src/index.html b/docs/src/index.html new file mode 100644 index 0000000000..e84b405234 --- /dev/null +++ b/docs/src/index.html @@ -0,0 +1,36 @@ + + + + + + Open MCT Web Documentation + + + Sections: + + + diff --git a/package.json b/package.json index 1216935f21..d1d675df19 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,21 @@ "karma-jasmine": "^0.1.5", "karma-phantomjs-launcher": "^0.1.4", "karma-requirejs": "^0.2.2", - "requirejs": "^2.1.17" + "requirejs": "^2.1.17", + "marked": "^0.3.5", + "glob": ">= 3.0.0", + "split": "^1.0.0", + "mkdirp": "^0.5.1", + "nomnoml": "^0.0.3", + "canvas": "^1.2.7" }, "scripts": { "start": "node app.js", "test": "karma start --single-run", "jshint": "jshint platform example || exit 0", - "jsdoc": "jsdoc -c jsdoc.json -r -d docs" + "jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api", + "otherdoc": "node docs/gendocs.js --in docs/src --out target/docs", + "docs": "npm run jsdoc ; npm run otherdoc" }, "repository": { "type": "git", diff --git a/platform/commonUI/browse/res/templates/create/create-menu.html b/platform/commonUI/browse/res/templates/create/create-menu.html index 3386d2d266..24314a17c7 100644 --- a/platform/commonUI/browse/res/templates/create/create-menu.html +++ b/platform/commonUI/browse/res/templates/create/create-menu.html @@ -23,9 +23,8 @@ - \ No newline at end of file + diff --git a/platform/features/layout/src/LayoutController.js b/platform/features/layout/src/LayoutController.js index 1c58f92988..643ed952a3 100644 --- a/platform/features/layout/src/LayoutController.js +++ b/platform/features/layout/src/LayoutController.js @@ -27,7 +27,8 @@ define( "use strict"; var DEFAULT_DIMENSIONS = [ 12, 8 ], - DEFAULT_GRID_SIZE = [32, 32]; + DEFAULT_GRID_SIZE = [ 32, 32 ], + MINIMUM_FRAME_SIZE = [ 320, 180 ]; /** * The LayoutController is responsible for supporting the @@ -67,12 +68,22 @@ define( }; } + // Generate default positions for a new panel + function defaultDimensions() { + return MINIMUM_FRAME_SIZE.map(function (min, i) { + return Math.max( + Math.ceil(min / gridSize[i]), + DEFAULT_DIMENSIONS[i] + ); + }); + } + // Generate a default position (in its raw format) for a frame. // Use an index to ensure that default positions are unique. function defaultPosition(index) { return { position: [index, index], - dimensions: DEFAULT_DIMENSIONS + dimensions: defaultDimensions() }; } @@ -107,6 +118,18 @@ define( ids.forEach(populatePosition); } + // Update grid size when it changed + function updateGridSize(layoutGrid) { + var oldSize = gridSize; + + gridSize = layoutGrid || DEFAULT_GRID_SIZE; + + // Only update panel positions if this actually changed things + if (gridSize[0] !== oldSize[0] || gridSize[1] !== oldSize[1]) { + lookupPanels(Object.keys(positions)); + } + } + // Position a panel after a drop event function handleDrop(e, id, position) { if (e.defaultPrevented) { @@ -138,6 +161,9 @@ define( e.preventDefault(); } + // Watch for changes to the grid size in the model + $scope.$watch("model.layoutGrid", updateGridSize); + // Position panes when the model field changes $scope.$watch("model.composition", lookupPanels); diff --git a/platform/features/layout/test/LayoutControllerSpec.js b/platform/features/layout/test/LayoutControllerSpec.js index 5ec80755f4..b0ab7a13fb 100644 --- a/platform/features/layout/test/LayoutControllerSpec.js +++ b/platform/features/layout/test/LayoutControllerSpec.js @@ -175,6 +175,23 @@ define( ); expect(testConfiguration.panels.d).not.toBeDefined(); }); + + it("ensures a minimum frame size", function () { + var styleB, styleC; + + // Start with a very small frame size + testModel.layoutGrid = [ 1, 1 ]; + + // White-boxy; we know which watch is which + mockScope.$watch.calls[0].args[1](testModel.layoutGrid); + mockScope.$watch.calls[1].args[1](testModel.composition); + + styleB = controller.getFrameStyle("b"); + + // Resulting size should still be reasonably large pixel-size + expect(parseInt(styleB.width, 10)).toBeGreaterThan(63); + expect(parseInt(styleB.width, 10)).toBeGreaterThan(31); + }); }); } ); diff --git a/protractor/README b/protractor/README new file mode 100644 index 0000000000..5734e5702d --- /dev/null +++ b/protractor/README @@ -0,0 +1,69 @@ +E2e Protractor Tests. + +1. Instructions: + + 1. 3 Control Scripts located in bin/. + run.js : node script used to start tests + start.js: node script used to setup test(starts node,localstorage and webdriver) + stop.js : node script, kills the 3 process started in start.js. + clean.js: node script used to remove the node_module directory.(clean up directory). + + 2. Use npm(Node Package Mangager) to Run Scripts. + a. cd protractor; + b. npm install; + c. To Run: + -npm start : will start processes need by protractor + -npm stop : will stop the processes need by protractor + -npm run-script run : will execute Protractor Script + -npm run-script all : will execute "start", "run", and "stop" script + +2. Directory Hierachy: + + -protractor: base directory + -common: contains prototype javascript functions that all other tests use. + -Buttons: common prototype functions related to enter fullscreen + -CreateItem: common prototype functions related to creating an item + -drag: common functions to test drag and drop. + -editItem: common functions used to test edit functionality. + -Launch: common script used to navigate the specified website. + -RightMenu: common functions for right click menu(remove). + -create + -e2e tests that creates the specified object. + -delete + -e2e tests that removes the specified object + -logs + -ctrl.sh redirects console output of MMAP, webdriver and elastic search and pipes them to log files. + -UI + -Contains tests that test the UI(drag drop, fullscreen, info bubble) + -conf.js: + -protractor config file. Explained below + -stressTest: + Tests that are used to test for memory leaks. You can use the new tab option on WARP and then open the + timeline in the new tab during the browser.sleep(). Once the test is do the browser will pause and you + can look a the timeline results in the new tab. + + NOTE: Cannot open chrome dev tools on same tab as the test are run on. Protractor uses the dev tools to + exectute the tests. + + -StressTest will create and delete folders. + -StressTestBubble.js: creates manny bubbles. + (Delay variable in InfoGesture.js was changed to 0) +3. Conf.js + Conf.js is used by protractor to setup and execute the tests. + -allScriptsTimeout: gives more time for protractor to synchronize with the page. + -jasmineNodeOpts: Protractor uses jasmine for the tests and jasmine has a default time out 30 seconds + per "it" test. Changed to maximume allowed time 360000 ms + -seleniumAddress: Protractor uses a Selenium server as a "proxy" between the test scripts and the browser + driver. A stand a lone version comes with protractor and to run use "webdriver-manager" + default address is: http://localhost:4444/wd/hub. + -specs[]: Is an array of files. Each File should have a "describe, it" test to be executed by protractor. + -capabilities: Tells protractor what browser to use and any browser arguments. + +4. bundle.json + bundle.json is used by npm to determine dependencies and location of script files. + -Dependencies: + "protractor": Contains protractor and webdriver package. + "psnode": Window/Unix Command, used for list/kill process.(ps aux) + "shelljs": Window/Unix Common JS Commands. eg rm,ls,exec + "sleep": Window/Unix Commands used to sleep the script + "string": Window/Unix Commands for string manipulation. \ No newline at end of file diff --git a/protractor/UI/Fullscreen.js b/protractor/UI/Fullscreen.js index 3c1c785228..532ad318d8 100644 --- a/protractor/UI/Fullscreen.js +++ b/protractor/UI/Fullscreen.js @@ -22,7 +22,7 @@ //TODO Add filter for duplications/ var fullScreenFile = require("../common/Buttons"); -describe('Test Fullscreen', function() { +describe('Enable Fullscreen', function() { var fullScreenClass = new fullScreenFile(); beforeEach(require('../common/Launch')); diff --git a/protractor/UI/InfoBubble.js b/protractor/UI/InfoBubble.js index 274b7966b0..c88e9018d0 100644 --- a/protractor/UI/InfoBubble.js +++ b/protractor/UI/InfoBubble.js @@ -25,7 +25,7 @@ var itemEdit = require("../common/EditItem"); var rightMenu = require("../common/RightMenu"); var Drag = require("../common/drag"); -describe('Test Info Bubble', function() { +describe('Info Bubble', function() { var fullScreenClass = new fullScreenFile(); var createClass = new createItem(); var editItemClass = new itemEdit(); diff --git a/protractor/UI/NewWindow.js b/protractor/UI/NewWindow.js index 7a714d79a2..1e98bbc8b4 100644 --- a/protractor/UI/NewWindow.js +++ b/protractor/UI/NewWindow.js @@ -24,7 +24,7 @@ var createClassFile = require("../common/CreateItem") var itemEdit = require("../common/EditItem"); var rightMenu = require("../common/RightMenu.js"); -describe('Test New Window', function() { +describe('New Window', function() { var fullScreenClass = new fullScreenFile(); var createClass = new createClassFile(); var editItemClass = new itemEdit(); diff --git a/protractor/UI/RightClick.js b/protractor/UI/RightClick.js index 0ae4dd0708..5f7d389313 100644 --- a/protractor/UI/RightClick.js +++ b/protractor/UI/RightClick.js @@ -21,28 +21,64 @@ *****************************************************************************/ var right_click = require("../common/RightMenu.js"); var Create = require("../common/CreateItem") -describe('Right Click Interations', function() { +var itemEdit = require("../common/EditItem"); + +describe('The Right Menu', function() { var clickClass = new right_click(); var createClass = new Create(); + var editItemClass = new itemEdit(); var ITEM_NAME = "Folder"; var ITEM_TYPE = "folder"; var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; beforeEach(require('../common/Launch')); - it('should delete the specified object', function(){ - createClass.createButton().click(); - var folder = createClass.selectNewItem(ITEM_TYPE); - expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]); - browser.sleep(1000); - folder.click() - browser.sleep(1000); - browser.wait(function () { - return element.all(by.model('ngModel[field]')).isDisplayed(); + it('should Dissapear After Delete', function(){ + browser.wait(function() { + createClass.createButton().click(); + return true; + }).then(function (){ + var folder = createClass.selectNewItem(ITEM_TYPE); + expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]); + browser.sleep(1000); + folder.click() + }).then(function() { + browser.wait(function () { + return element.all(by.model('ngModel[field]')).isDisplayed(); + }) + createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click(); + browser.sleep(1000); + }).then(function (){ + var item = editItemClass.SelectItem(ITEM_GRID_SELECT); + expect(item.count()).toBe(1); + browser.sleep(1000); + }).then(function () { + var MyItem = ">\nF\nMy Items" + element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === MyItem; + }); + }).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + var object = element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === ">\nF\nFolder"; + }); + }); + browser.sleep(1000) + browser.actions().mouseMove(object.get(0)).perform(); + browser.actions().click(protractor.Button.RIGHT).perform(); + browser.sleep(1000) + var menu = element.all(by.css('.ng-binding')).filter(function (ele){ + return ele.getText().then(function (text) { + return text == "Z\nRemove"; + }) + }) + menu.click(); + browser.sleep(1000) + + expect(menu.isDisplayed()).toBe(false); }) - createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click(); - clickClass.delete(ITEM_NAME); - browser.sleep(1000); }); }); diff --git a/protractor/bin/clean.js b/protractor/bin/clean.js new file mode 100755 index 0000000000..82e776901f --- /dev/null +++ b/protractor/bin/clean.js @@ -0,0 +1,15 @@ +#! /usr/bin/env node +var shell = require("shelljs/global"); + +var startdir = process.cwd(); +var command = "npm unlink"; + +console.log("Cleaning Directory") +exec(command, function(code, output) { + if(code != 0){ + console.log('Exit code:', code); + console.log('Program output:', output); + } +}); +console.log("rm -rf node_modules") +rm('-rf', __dirname + "/../node_modules") diff --git a/protractor/bin/ctrl.sh b/protractor/bin/ctrl.sh new file mode 100755 index 0000000000..faf385d2ad --- /dev/null +++ b/protractor/bin/ctrl.sh @@ -0,0 +1,90 @@ +#! /bin/bash +ARGUMENT=$1; + +if [ $# != 1 ]; then + echo "Expected 1 Aurgument. Received " $# 1>&2; + exit 1 +fi +#Start webdrive and http-server +if [ $ARGUMENT == start ]; then + echo "Creating Log Directory ..." + mkdir logs; + + cd .. + node app.js -p 1984 -x platform/persistence/elastic -i example/persistence > protractor/logs/nodeApp.log 2>&1 & + sleep 3; + if grep -iq "Error" protractor/logs/nodeApp.log; then + if grep -iq "minimist" protractor/logs/nodeApp.log; then + echo " Node Failed Because Minimist is not installed" + echo " Installng Minimist ..." + npm install minimist express > protractor/logs/minimist.log 2>&1 & + wait $! + if [ $? != 0 ]; then + echo " Error: minimist" + echo " Check Log file" + echo + else + echo " Started: Minimist" + echo + node app.js -p 1984 -x platform/persistence/elastic -i example/persistence > protractor/logs/nodeApp.log 2>&1 & + if grep -iq "Error" protractor/logs/nodeApp.log; then + echo " Error: node app failed" + echo " Check Log file" + echo + else + echo " Started: node app.js" + echo + fi + fi + else + echo " Error: node app failed" + echo " Check Log file" + echo + fi + else + echo " Started: node app.js" + echo + fi + echo "Starting webdriver ..." + + cd protractor; + webdriver-manager start > logs/webdriver.log 2>&1 & + sleep 3; + if grep -iq "Exception" logs/webdriver.log; then + echo " Error: webdriver-manager" + echo " Check Log file" + echo + else + echo " Started: webdriver-manager" + fi + echo "Starting Elastic Search..." + + elasticsearch > logs/elasticSearch.log 2>&1 & + sleep 3; + if grep -iq "Exception" logs/elasticSearch.log; then + echo " Error: ElasticSearch" + echo " Check Log file" + echo + else + echo " Started: ElasticSearch" + fi +#Runs Protractor tests +elif [ $ARGUMENT == run ]; then + protractor ./conf.js +#Kill Process +elif [ $ARGUMENT == stop ]; then + echo "Removing logs" + rm -rf logs + echo "Stopping Node" + kill $(ps aux | grep "[n]ode app.js"| awk '{print $2}'); + + echo "Stopping webdriver ..." + kill $(ps aux | grep "[p]rotractor" | awk '{print $2}'); + kill $(ps aux | grep "[w]ebdriver-manager" | awk '{print $2}'); + sleep 1; + echo "Stopping Elastic..." + kill $(ps aux | grep "[e]lastic" | awk '{print $2}'); + sleep 1; +else + echo "Unkown: Command" $1; +fi diff --git a/protractor/bin/run.js b/protractor/bin/run.js new file mode 100755 index 0000000000..316caa11d0 --- /dev/null +++ b/protractor/bin/run.js @@ -0,0 +1,12 @@ +#! /usr/bin/env node +var shell = require("shelljs/global"); +var sleep = require('sleep'); + +var command = __dirname + "/../node_modules/protractor/bin/protractor " +__dirname + "/../conf.js"; +console.log("Executing Protractor Test") +exec(command, function(code, output) { + if(code != 0){ + console.log('Exit code:', code); + console.log('Program output:', output); + } +}); \ No newline at end of file diff --git a/protractor/bin/start.js b/protractor/bin/start.js new file mode 100755 index 0000000000..21aacc7efe --- /dev/null +++ b/protractor/bin/start.js @@ -0,0 +1,40 @@ +#! /usr/bin/env node +var shell,sleep; +try { + shell = require("shelljs/global"); + sleep = require('sleep'); +}catch (e){ + console.log("Dependencies Error"); + console.log("Run npm install"); + throw (e); +} +///Users/jsanderf/git/elastic/wtd/protractor/bin +var startdir = process.cwd(); +var command; +mkdir(__dirname + '/../logs'); + +command = __dirname + "/../node_modules/protractor/bin/webdriver-manager update"; +console.log("Installing Webdriver"); +exec(command,{async:false}); +sleep.sleep(1); + +console.log(); +cd(__dirname + '/../../'); +console.log('Installing Dependencies'); +exec("npm install minimist express", {async:false}); +console.log('Starting Node'); +sleep.sleep(1); +exec("node app.js -p 1984 -x example/persistence -x platform/persistence/elastic -i example/localstorage > protractor/logs/nodeApp.log 2>&1 &", {async:false}); +console.log(' Started Node'); + +console.log(); +console.log('Starting Webdriver'); +sleep.sleep(1); +exec("protractor/node_modules/protractor/bin/webdriver-manager start --standalone> protractor/logs/webdriver.log 2>&1 &",{async:false}); +if(error() == null){ + console.log(" Webdriver Started"); +}else{ + console.log(" Error : ", error()); +} +sleep.sleep(1); +cd(startdir); diff --git a/protractor/bin/stop.js b/protractor/bin/stop.js new file mode 100755 index 0000000000..ac2c3b4295 --- /dev/null +++ b/protractor/bin/stop.js @@ -0,0 +1,44 @@ +#! /usr/bin/env node + +var shell = require("shelljs/global"); +var ps = require('psnode'); +var S = require('string'); +var sleep = require('sleep'); + +// A simple pid lookup +ps.list(function(err, results) { + + results.forEach(function( process ){ + //Killing Node + if(S(process.command).contains("node app.js")) { + console.log(); + console.log( 'Killing Node: %s', process.command); + ps.kill(process.pid, function(err, stdout) { + if (err) { + throw new Error(err); + } + console.log(stdout); + }); + }else if(S(process.command).contains("webdriver")) { + console.log(); + console.log( 'Killing WebDriver: %s', process.command); + ps.kill(process.pid, function(err, stdout) { + if (err){ + throw new Error(err); + } + console.log(stdout); + }); + }else if(S(process.command).contains("protractor")) { + console.log(); + console.log( 'Killing Chrome Drive: %s', process.command); + ps.kill(process.pid, function(err, stdout) { + if (err){ + throw new Error(err); + } + console.log(stdout); + }); + } + }); +}); + + diff --git a/protractor/common/Launch.js b/protractor/common/Launch.js index 2b700672cc..fd745f94c3 100644 --- a/protractor/common/Launch.js +++ b/protractor/common/Launch.js @@ -24,6 +24,6 @@ module.exports = function launch() { 'use strict'; browser.ignoreSynchronization = true; - browser.get('http://localhost:1984/'); - browser.sleep(2000); // 20 seconds + browser.get('http://localhost:1984'); + browser.sleep(2000); // 2 seconds }; diff --git a/protractor/common/RightMenu.js b/protractor/common/RightMenu.js index 490d876d87..d1375d0533 100644 --- a/protractor/common/RightMenu.js +++ b/protractor/common/RightMenu.js @@ -24,18 +24,25 @@ var RightMenu = (function () { function RightMenu() { } + function carrotMyItem(){ + var MyItem = ">\nF\nMy Items" + element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === MyItem; + }); + }).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + } //RightMenu Click on Object RightMenu.prototype.delete = function (name, flag) { if(typeof flag === 'undefined'){ flag = true; } if(flag === true){ - var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).get(0).click(); + carrotMyItem(); } browser.sleep(1000) var object = element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - //expect(text).toEqual("3"); return text === name; }); }); @@ -43,7 +50,7 @@ var RightMenu = (function () { browser.actions().mouseMove(object.get(0)).perform(); browser.actions().click(protractor.Button.RIGHT).perform(); browser.sleep(1000) - var remove = element.all(by.css('.ng-binding')).filter(function (ele){ + element.all(by.css('.ng-binding')).filter(function (ele){ return ele.getText().then(function (text) { return text == "Z\nRemove"; }) @@ -58,11 +65,10 @@ var RightMenu = (function () { }); }; RightMenu.prototype.reset = function (name) { - var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + carrotMyItem(); browser.sleep(1000) var object = element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - //expect(text).toEqual("3"); return text === name; }); }).click(); @@ -75,19 +81,19 @@ var RightMenu = (function () { return text == "r\nRestart at 0"; }) }).click(); + browser.sleep(1000) }; + //click '<', true==yes false==no RightMenu.prototype.select = function(name, flag){ if(typeof flag == "undefined"){ flag = true; } - //click '<', true==yes false==no if(flag == true){ - var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + carrotMyItem(); } browser.sleep(1000) return element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - // expect(text).toEqual("3"); return text === name; }); }); @@ -96,7 +102,6 @@ var RightMenu = (function () { RightMenu.prototype.dragDrop = function(name){ var object = element.all(by.repeater('child in composition')).filter(function (ele){ return ele.getText().then(function(text) { - //expect(text).toEqual("3"); return text === name; }); }); diff --git a/protractor/conf.js b/protractor/conf.js index c33e196157..8b828fcbd7 100644 --- a/protractor/conf.js +++ b/protractor/conf.js @@ -24,34 +24,34 @@ // conf.js exports.config = { allScriptsTimeout: 500000, - defaultTimeoutInterval: 60000, + jasmineNodeOpts: {defaultTimeoutInterval: 360000}, seleniumAddress: 'http://localhost:4444/wd/hub', - //specs: ['StressTest.js'], + //specs: ['StressTestCarrot.js'], specs: [ - //'create/CreateActivity.js', - //'delete/DeleteActivity.js', - //'create/CreateActivityMode.js', - //'delete/DeleteActivityMode.js', - //'create/CreateActivityMode.js', - //'create/CreateClock.js', - //'delete/DeleteClock.js', + // 'create/CreateActivity.js', + // 'delete/DeleteActivity.js', + // 'create/CreateActivityMode.js', + // 'delete/DeleteActivityMode.js', + // 'create/CreateClock.js', + // 'delete/DeleteClock.js', 'create/CreateDisplay.js', - //'delete/DeleteDisplay.js', + 'delete/DeleteDisplay.js', 'create/CreateFolder.js', - //'delete/DeleteFolder.js', - 'create/CreateTelemetry.js', - //'delete/DeleteTelemetry.js', - //'create/CreateTimeline.js', - //'delete/DeleteTimeline.js', - //'create/CreateTimer.js', - //'delete/DeleteTimer.js', + 'delete/DeleteFolder.js', + // 'create/CreateTelemetry.js', + // 'delete/DeleteTelemetry.js', + // 'create/CreateTimeline.js', + // 'delete/DeleteTimeline.js', + // 'create/CreateTimer.js', + // 'delete/DeleteTimer.js', 'create/CreateWebPage.js', - //'delete/DeleteWebPage.js', + 'delete/DeleteWebPage.js', 'UI/Fullscreen.js', 'create/CreateButton.js', //"UI/DragDrop.js", - //"UI/NewWindow.js", - 'UI/InfoBubble.js' + "UI/NewWindow.js" + //'UI/InfoBubble.js', + //'UI/RightClick.js' ], capabilities: { 'browserName': 'chrome', // or 'safari' @@ -61,7 +61,7 @@ exports.config = { // Allow specifying binary location as an environment variable, // for cases where Chrome is not installed in a usual location. -if (process.env.PROTRACTOR_CHROME_BINARY) { +if (process.env.CHROME_BIN) { exports.config.capabilities.chromeOptions.binary = - process.env.PROTRACTOR_CHROME_BINARY; + process.env.CHROME_BIN; } diff --git a/protractor/create/CreateActivityMode.js b/protractor/create/CreateActivityMode.js index e9469749aa..17ed700ae6 100644 --- a/protractor/create/CreateActivityMode.js +++ b/protractor/create/CreateActivityMode.js @@ -22,7 +22,7 @@ var itemCreate = require("../common/CreateItem"); var itemEdit = require("../common/EditItem"); -describe('Create Web Page', function() { +describe('Create Activity Mode', function() { var createClass = new itemCreate(); var editItemClass = new itemEdit(); var ITEM_NAME = "Activity Mode"; diff --git a/protractor/create/CreateClock.js b/protractor/create/CreateClock.js index d1dc781b58..940db62af4 100644 --- a/protractor/create/CreateClock.js +++ b/protractor/create/CreateClock.js @@ -57,7 +57,7 @@ describe('Create Clock', function() { }); it('should check clock', function () { - function getTime() { + function getTime(flag) { function addZero(time){ if(time < 10){ return '0' + time; @@ -66,7 +66,6 @@ describe('Create Clock', function() { } var currentdate = new Date(); - var month = currentdate.getMonth() + 1; month = addZero(month); @@ -77,6 +76,9 @@ describe('Create Clock', function() { hour = addZero(hour); var second = currentdate.getSeconds(); + if(flag == true) { + second = second + 1; + } second = addZero(second); var minute = currentdate.getMinutes(); @@ -85,17 +87,23 @@ describe('Create Clock', function() { return ("UTC " + currentdate.getFullYear() + "/" + (month) + "/" + day + " " + (hour) + ":" + minute + ":" + second + " PM"); } - - var current,clock; - rightClickClass.select(ITEM_MENU_GLYPH, true).click().then(function () { - browser.sleep(1000); - current = browser.executeScript(getTime); - }).then(function () { - clock = element(by.css('.l-time-display.l-digital.l-clock.s-clock.ng-scope')); - clock.getText().then(function (time) { - expect(current).toEqual(time); - }) + this.addMatchers({ + toBeIn: function(expected){ + var posibilities = Array.isArray(this.actual) ? this.actual : [this.actual]; + return posibilities.indexOf(expected) > -1; + } }) - + rightClickClass.select(ITEM_MENU_GLYPH, true).click().then(function () { + browser.sleep(1000); + browser.executeScript(getTime, false).then(function(current){ + browser.executeScript(getTime, true).then(function(current1) { + var clock = element(by.css('.l-time-display.l-digital.l-clock.s-clock.ng-scope')); + clock.getText().then(function (ele) { + expect([current,current1]).toBeIn(ele); + }) + }); + }); + + }) }); }); diff --git a/protractor/create/CreateTimer.js b/protractor/create/CreateTimer.js index d8059c1f59..e83fedfedf 100644 --- a/protractor/create/CreateTimer.js +++ b/protractor/create/CreateTimer.js @@ -63,7 +63,11 @@ describe('Create Timer', function() { browser.sleep(1000) var timer = element(by.css('.value.ng-binding.active')) timer.getText().then(function (time) { - expect(time).toEqual("0D 00:00:01") + var timerChecker = false; + if(time == "0D 00:00:01" || time == "0D 00:00:02"){ + timerChecker = true; + } + expect(timerChecker).toEqual(true) }) }); diff --git a/protractor/package.json b/protractor/package.json new file mode 100644 index 0000000000..b43b9b1cdb --- /dev/null +++ b/protractor/package.json @@ -0,0 +1,20 @@ +{ + "name": "ProtractorLauncher", + "version": "1.0.0", + "scripts" : { + "start" : "bin/start.js", + "protractor" : "bin/run.js", + "stop" : "bin/stop.js", + "all" : "bin/start.js; bin/run.js; bin/stop.js;", + "clean" : "bin/clean.js" + }, + "dependencies": { + "protractor": "^2.1.0", + "psnode": "0.0.1", + "shelljs": "^0.5.2", + "sleep": "^3.0.0", + "string": "^3.3.1" + }, + "description": "E2e Protractor Tests.", + "license": "ISC" +} diff --git a/protractor/StressTest.js b/protractor/stressTest/StressTest.js similarity index 74% rename from protractor/StressTest.js rename to protractor/stressTest/StressTest.js index 5e4a8b1507..108e431868 100644 --- a/protractor/StressTest.js +++ b/protractor/stressTest/StressTest.js @@ -19,10 +19,9 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -//TODO Add filter for duplications/ -var itemCreate = require("./common/CreateItem"); -var itemEdit = require("./common/EditItem"); -var right_click = require("./common/RightMenu.js"); +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); describe('Create Folder', function() { var clickClass = new right_click(); @@ -41,31 +40,35 @@ describe('Create Folder', function() { }); it('should Create new Folder', function(){ browser.sleep(5000); - for(var i=0; i < 50; i++){ + for(var i=0; i < 25; i++){ browser.wait(function() { createClass.createButton().click(); return true; }).then(function (){ var folder = createClass.selectNewItem(ITEM_TYPE); expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]); - browser.sleep(1000); + browser.sleep(500); folder.click() }).then(function() { browser.wait(function () { return element.all(by.model('ngModel[field]')).isDisplayed(); }) createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click(); - browser.sleep(1000); + browser.sleep(500); }).then(function (){ - browser.sleep(1000); - // if(i === 1){ - clickClass.delete(ITEM_SIDE_SELECT, true); - element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); - // }else{ - browser.sleep(1000); - + browser.sleep(500); + clickClass.delete(ITEM_SIDE_SELECT, true); + //element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); + + + var MyItem = ">\nF\nMy Items" + element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + //expect(text).toEqual(MyItem); + return text === MyItem; + }); + }).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click(); // clickClass.delete(ITEM_SIDE_SELECT, false); - // } }); } browser.pause(); diff --git a/protractor/stressTest/StressTestBubble.js b/protractor/stressTest/StressTestBubble.js new file mode 100644 index 0000000000..b06b29c1b9 --- /dev/null +++ b/protractor/stressTest/StressTestBubble.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/StressTestBubble.jsStressTestBubble.js +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(10000); + for(var i=0; i < 1000; i++){ + var object = element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === ">\nF\nMy Items"; + }); + }); + //browser.sleep(1000) + browser.actions().mouseMove(object.get(0)).perform(); + //browser.actions().click(protractor.Button.RIGHT).perform(); + + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestCreateButton.js b/protractor/stressTest/StressTestCreateButton.js new file mode 100644 index 0000000000..25debf3bba --- /dev/null +++ b/protractor/stressTest/StressTestCreateButton.js @@ -0,0 +1,56 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(10000); + for(var i=0; i < 1000; i++){ + createClass.createButton().click(); + + //browser.sleep(1000) + //browser.actions().mouseMove(object.get(0)).perform(); + //browser.actions().click(protractor.Button.RIGHT).perform(); + + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestMenu.js b/protractor/stressTest/StressTestMenu.js new file mode 100644 index 0000000000..d6e30bc5b2 --- /dev/null +++ b/protractor/stressTest/StressTestMenu.js @@ -0,0 +1,55 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(10000); + for(var i=0; i < 1000; i++){ + browser.wait(function() { + createClass.createButton().click(); + return true; + }).then(function (){ + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + }) + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestNewPage.js b/protractor/stressTest/StressTestNewPage.js new file mode 100644 index 0000000000..2b0e82fbb1 --- /dev/null +++ b/protractor/stressTest/StressTestNewPage.js @@ -0,0 +1,61 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); +var fullScreenFile = require("../common/FullScreen"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var fullScreenClass = new fullScreenFile(); + + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(15000); + for(var i=0; i < 1000; i++){ + fullScreenClass.newWidnow().click(); + + browser.getAllWindowHandles().then(function (handles) { + //browser.driver.switchTo().window(handles[1]); + browser.sleep(1000); + browser.driver.close(); + browser.sleep(1000); + // browser.driver.switchTo().window(handles[0]); + }); + } + browser.pause(); + + }); + +}); diff --git a/protractor/stressTest/StressTestRightClick.js b/protractor/stressTest/StressTestRightClick.js new file mode 100644 index 0000000000..f16f876a90 --- /dev/null +++ b/protractor/stressTest/StressTestRightClick.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +var itemCreate = require("../common/CreateItem"); +var itemEdit = require("../common/EditItem"); +var right_click = require("../common/RightMenu.js"); + +describe('Create Folder', function() { + var clickClass = new right_click(); + var createClass = new itemCreate(); + var editItemClass = new itemEdit(); + var ITEM_NAME = "Folder"; + var ITEM_TYPE = "folder"; + var ITEM_MENU_GLYPH = 'F\nFolder'; + var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items'; + var ITEM_SIDE_SELECT = ">\nF\nFolder" + + beforeEach(function() { + browser.ignoreSynchronization = true; + browser.get('http://localhost:1984/warp/'); + browser.sleep(2000); // 20 seconds + }); + it('should Create new Folder', function(){ + browser.sleep(8000); + for(var i=0; i < 1000; i++){ + var object = element.all(by.repeater('child in composition')).filter(function (ele){ + return ele.getText().then(function(text) { + return text === ">\nF\nMy Items"; + }); + }); + //browser.sleep(1000) + browser.actions().mouseMove(object.get(0)).perform(); + browser.actions().click(protractor.Button.RIGHT).perform(); + + element.all(by.css('.items-holder.grid.abs.ng-scope')).click(); + } + browser.pause(); + + }); + +});