├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── app ├── boot │ └── page.js ├── component_data │ ├── compose_box.js │ ├── mail_items.js │ └── move_to.js ├── component_ui │ ├── compose_box.js │ ├── folders.js │ ├── mail_controls.js │ ├── mail_items.js │ ├── move_to_selector.js │ └── with_select.js ├── css │ └── custom.css ├── data.js └── templates.js ├── bower_components ├── bootstrap │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ └── bootstrap.min.css │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ │ ├── bootstrap.js │ │ └── bootstrap.min.js ├── es5-shim │ ├── .gitignore │ ├── CHANGES │ ├── CONTRIBUTORS.md │ ├── LICENSE │ ├── README.md │ ├── component.json │ ├── es5-sham.js │ ├── es5-sham.min.js │ ├── es5-shim.js │ ├── es5-shim.min.js │ ├── package.json │ └── tests │ │ ├── helpers │ │ ├── h-kill.js │ │ ├── h-matchers.js │ │ └── h.js │ │ ├── index.html │ │ ├── lib │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ ├── jasmine.js │ │ ├── jasmine_favicon.png │ │ └── json2.js │ │ └── spec │ │ ├── s-array.js │ │ ├── s-date.js │ │ ├── s-function.js │ │ ├── s-object.js │ │ └── s-string.js ├── flight │ ├── .travis.yml │ └── lib │ │ ├── advice.js │ │ ├── base.js │ │ ├── component.js │ │ ├── compose.js │ │ ├── debug.js │ │ ├── index.js │ │ ├── logger.js │ │ ├── registry.js │ │ └── utils.js ├── jasmine-flight │ ├── .bower.json │ ├── LICENSE.md │ ├── README.md │ ├── bower.json │ └── lib │ │ └── jasmine-flight.js ├── jasmine-jquery │ ├── .bower.json │ ├── CONTRIBUTING.md │ ├── Gruntfile.js │ ├── MIT.LICENSE │ ├── README.md │ ├── bower.json │ ├── lib │ │ └── jasmine-jquery.js │ ├── package.json │ └── spec │ │ ├── fixtures │ │ ├── json │ │ │ └── jasmine_json_test.json │ │ ├── real_non_mocked_fixture.html │ │ └── real_non_mocked_fixture_style.css │ │ └── suites │ │ └── jasmine-jquery-spec.js ├── jquery │ ├── component.json │ ├── composer.json │ ├── jquery.js │ └── jquery.min.js ├── mustache │ └── mustache.js └── requirejs │ └── require.js ├── index.html ├── karma.conf.js ├── package.json ├── requireMain.js └── test ├── fixtures ├── compose_box.html ├── mail_controls.html ├── mail_items.html └── move_to_selector.html ├── spec ├── component_data │ ├── compose_box_spec.js │ ├── mail_items_spec.js │ └── move_to_spec.js └── component_ui │ ├── compose_box_spec.js │ ├── folders_spec.js │ ├── mail_controls_spec.js │ ├── mail_items_spec.js │ ├── move_to_selector_spec.js │ └── with_select_spec.js └── test-main.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - "0.10" 4 | - "0.8" 5 | before_script: 6 | - "export DISPLAY=:99.0" 7 | - "sh -e /etc/init.d/xvfb start" 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Twitter Inc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flight example app 2 | 3 | [![Build Status](https://travis-ci.org/flightjs/example-app.png?branch=master)](http://travis-ci.org/flightjs/example-app) 4 | 5 | An example Flight application. [Demo here](http://flightjs.github.io/example-app/) 6 | -------------------------------------------------------------------------------- /app/boot/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'app/component_data/mail_items', 7 | 'app/component_data/compose_box', 8 | 'app/component_data/move_to', 9 | 'app/component_ui/mail_items', 10 | 'app/component_ui/mail_controls', 11 | 'app/component_ui/compose_box', 12 | 'app/component_ui/folders', 13 | 'app/component_ui/move_to_selector' 14 | ], 15 | 16 | function( 17 | MailItemsData, 18 | ComposeBoxData, 19 | MoveToData, 20 | MailItemsUI, 21 | MailControlsUI, 22 | ComposeBoxUI, 23 | FoldersUI, 24 | MoveToSelectorUI) { 25 | 26 | function initialize() { 27 | MailItemsData.attachTo(document); 28 | ComposeBoxData.attachTo(document, { 29 | selectedFolders: ['inbox'] 30 | }); 31 | MoveToData.attachTo(document); 32 | MailItemsUI.attachTo('#mail_items', { 33 | itemContainerSelector: '#mail_items_TB', 34 | selectedFolders: ['inbox'] 35 | }); 36 | MailControlsUI.attachTo('#mail_controls'); 37 | ComposeBoxUI.attachTo('#compose_box'); 38 | FoldersUI.attachTo('#folders'); 39 | MoveToSelectorUI.attachTo('#move_to_selector', { 40 | moveActionSelector: '#move_mail', 41 | selectedFolders: ['inbox'] 42 | }); 43 | } 44 | 45 | return initialize; 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /app/component_data/compose_box.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'flight/lib/component', 7 | 'bower_components/mustache/mustache', 8 | 'app/data', 9 | 'app/templates' 10 | ], 11 | 12 | function(defineComponent, Mustache, dataStore, templates) { 13 | return defineComponent(composeBox); 14 | 15 | function composeBox() { 16 | 17 | this.defaultAttrs({ 18 | dataStore: dataStore, 19 | recipientHintId: 'recipient_hint', 20 | subjectHint: 'Subject', 21 | messageHint: 'Message', 22 | toHint: 'To', 23 | forwardPrefix: 'Fw', 24 | replyPrefix: 'Re' 25 | }); 26 | 27 | this.serveComposeBox = function(ev, data) { 28 | this.trigger("dataComposeBoxServed", { 29 | markup: this.renderComposeBox(data.type, data.relatedMailId), 30 | type: data.type}); 31 | }; 32 | 33 | this.getSubject = function(type, relatedMailId) { 34 | var relatedMail = this.attr.dataStore.mail.filter(function(each) { 35 | return each.id == relatedMailId; 36 | })[0]; 37 | 38 | var subject = relatedMail && relatedMail.subject; 39 | 40 | var subjectLookup = { 41 | newMail: this.attr.subjectHint, 42 | forward: this.attr.forwardPrefix + ": " + subject, 43 | reply: this.attr.replyPrefix + ": " + subject 44 | } 45 | 46 | return subjectLookup[type]; 47 | }; 48 | 49 | this.renderComposeBox = function(type, relatedMailId) { 50 | var recipientId = this.getRecipientId(type, relatedMailId); 51 | var contacts = this.attr.dataStore.contacts.map(function(contact) { 52 | contact.recipient = (contact.id == recipientId); 53 | return contact; 54 | }); 55 | 56 | return Mustache.render(templates.composeBox, { 57 | newMail: type == 'newMail', 58 | reply: type == 'reply', 59 | subject: this.getSubject(type, relatedMailId), 60 | message: this.attr.messageHint, 61 | contacts: contacts 62 | }); 63 | }; 64 | 65 | this.getRecipientId = function(type, relatedMailId) { 66 | var relatedMail = (type == 'reply') && this.attr.dataStore.mail.filter(function(each) { 67 | return each.id == relatedMailId; 68 | })[0]; 69 | 70 | return relatedMail && relatedMail.contact_id || this.attr.recipientHintId; 71 | }; 72 | 73 | 74 | this.send = function(ev, data) { 75 | this.attr.dataStore.mail.push({ 76 | id: String(Date.now()), 77 | contact_id: data.to_id, 78 | folders: ["sent"], 79 | time: Date.now(), 80 | subject: data.subject, 81 | message: data.message 82 | }); 83 | this.trigger('dataMailItemsRefreshRequested', {folder: data.currentFolder}); 84 | }; 85 | 86 | this.after("initialize", function() { 87 | this.on("uiComposeBoxRequested", this.serveComposeBox); 88 | this.on("uiSendRequested", this.send); 89 | }); 90 | } 91 | 92 | } 93 | ); 94 | -------------------------------------------------------------------------------- /app/component_data/mail_items.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'flight/lib/component', 7 | 'bower_components/mustache/mustache', 8 | 'app/data', 9 | 'app/templates' 10 | ], 11 | 12 | function(defineComponent, Mustache, dataStore, templates) { 13 | return defineComponent(mailItems); 14 | 15 | function mailItems() { 16 | 17 | this.defaultAttrs({ 18 | folder: 'inbox', 19 | dataStore: dataStore 20 | }); 21 | 22 | this.serveMailItems = function(ev, data) { 23 | var folder = (data && data.folder) || this.attr.folder; 24 | this.trigger("dataMailItemsServed", {markup: this.renderItems(this.assembleItems(folder))}) 25 | }; 26 | 27 | this.renderItems = function(items) { 28 | return Mustache.render(templates.mailItem, {mailItems: items}); 29 | }; 30 | 31 | this.assembleItems = function(folder) { 32 | var items = []; 33 | 34 | this.attr.dataStore.mail.forEach(function(each) { 35 | if (each.folders && each.folders.indexOf(folder) > -1) { 36 | items.push(this.getItemForView(each)); 37 | } 38 | }, this); 39 | 40 | return items; 41 | }; 42 | 43 | this.getItemForView = function(itemData) { 44 | var thisItem, thisContact, msg 45 | 46 | thisItem = {id: itemData.id, important: itemData.important}; 47 | 48 | thisContact = this.attr.dataStore.contacts.filter(function(contact) { 49 | return contact.id == itemData.contact_id 50 | })[0]; 51 | thisItem.name = [thisContact.firstName, thisContact.lastName].join(' '); 52 | 53 | var subj = itemData.subject; 54 | thisItem.formattedSubject = subj.length > 70 ? subj.slice(0, 70) + "..." : subj; 55 | 56 | var msg = itemData.message; 57 | thisItem.formattedMessage = msg.length > 70 ? msg.slice(0, 70) + "..." : msg; 58 | 59 | return thisItem; 60 | }; 61 | 62 | this.after("initialize", function() { 63 | this.on("uiMailItemsRequested", this.serveMailItems); 64 | this.on("dataMailItemsRefreshRequested", this.serveMailItems); 65 | }); 66 | } 67 | } 68 | ); 69 | -------------------------------------------------------------------------------- /app/component_data/move_to.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'flight/lib/component', 7 | 'bower_components/mustache/mustache', 8 | 'app/data', 9 | 'app/templates' 10 | ], 11 | 12 | function(defineComponent, Mustache, dataStore, templates) { 13 | return defineComponent(moveTo); 14 | 15 | function moveTo() { 16 | 17 | this.defaultAttrs({ 18 | dataStore: dataStore 19 | }); 20 | 21 | this.serveAvailableFolders = function(ev, data) { 22 | this.trigger("dataMoveToItemsServed", { 23 | markup: this.renderFolderSelector(this.getOtherFolders(data.folder)) 24 | }) 25 | }; 26 | 27 | this.renderFolderSelector = function(items) { 28 | return Mustache.render(templates.moveToSelector, {moveToItems: items}); 29 | }; 30 | 31 | this.moveItems = function(ev, data) { 32 | var itemsToMoveIds = data.itemIds 33 | this.attr.dataStore.mail.forEach(function(item) { 34 | if (itemsToMoveIds.indexOf(item.id) > -1) { 35 | item.folders = [data.toFolder]; 36 | } 37 | }); 38 | this.trigger('dataMailItemsRefreshRequested', {folder: data.fromFolder}); 39 | }; 40 | 41 | this.getOtherFolders = function(folder) { 42 | return this.attr.dataStore.folders.filter(function(e) {return e != folder}); 43 | }; 44 | 45 | this.after("initialize", function() { 46 | this.on("uiAvailableFoldersRequested", this.serveAvailableFolders); 47 | this.on("uiMoveItemsRequested", this.moveItems); 48 | }); 49 | } 50 | 51 | } 52 | ); 53 | -------------------------------------------------------------------------------- /app/component_ui/compose_box.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'flight/lib/component' 7 | ], 8 | 9 | function(defineComponent) { 10 | 11 | return defineComponent(composeBox); 12 | 13 | function composeBox() { 14 | 15 | this.defaultAttrs({ 16 | newMailType: 'newMail', 17 | forwardMailType: 'forward', 18 | replyMailType: 'reply', 19 | hintClass: 'hint', 20 | selectedFolders: [], 21 | selectedMailItems: [], 22 | 23 | //selectors 24 | composeControl: '.compose', 25 | newControlSelector: '#new_mail', 26 | cancelSelector: '#cancel_composed', 27 | sendSelector: '#send_composed', 28 | toSelector: '#compose_to', 29 | subjectSelector: '#compose_subject', 30 | messageSelector: '#compose_message', 31 | recipientSelector: '#recipient_select', 32 | recipientHintSelector: '#recipient_hint', 33 | selectedRecipientSelector: '#recipient_select :selected', 34 | hintSelector: 'div.hint' 35 | }); 36 | 37 | this.newMail = function() { 38 | this.requestComposeBox(this.attr.newMailType); 39 | }; 40 | 41 | this.forward = function() { 42 | this.requestComposeBox(this.attr.forwardMailType, this.attr.selectedMailItems); 43 | }; 44 | 45 | this.reply = function() { 46 | this.requestComposeBox(this.attr.replyMailType, this.attr.selectedMailItems); 47 | }; 48 | 49 | this.requestComposeBox = function(type, relatedMailId) { 50 | this.trigger('uiComposeBoxRequested', {type: type, relatedMailId: relatedMailId}); 51 | }; 52 | 53 | this.launchComposeBox = function(ev, data) { 54 | var focusSelector = (data.type == this.attr.replyMailType) ? 'messageSelector' : 'toSelector'; 55 | this.$node.html(data.markup).show(); 56 | this.select(focusSelector).focus(); 57 | }; 58 | 59 | this.cancel = function() { 60 | this.$node.html('').hide(); 61 | }; 62 | 63 | this.requestSend = function() { 64 | var data = { 65 | to_id: this.select('selectedRecipientSelector').attr('id'), 66 | subject: this.select('subjectSelector').text(), 67 | message: this.select('messageSelector').text(), 68 | currentFolder: this.attr.selectedFolders[0] 69 | }; 70 | this.trigger('uiSendRequested', data); 71 | this.$node.hide(); 72 | }; 73 | 74 | this.enableSend = function() { 75 | this.select('recipientHintSelector').attr('disabled', 'disabled'); 76 | this.select('sendSelector').removeAttr('disabled'); 77 | }; 78 | 79 | this.removeHint = function(ev, data) { 80 | $(ev.target).html('').removeClass(this.attr.hintClass); 81 | }; 82 | 83 | this.updateMailItemSelections = function(ev, data) { 84 | this.attr.selectedMailItems = data.selectedIds; 85 | } 86 | 87 | this.updateFolderSelections = function(ev, data) { 88 | this.attr.selectedFolders = data.selectedIds; 89 | } 90 | 91 | this.after('initialize', function() { 92 | this.on(document, 'dataComposeBoxServed', this.launchComposeBox); 93 | this.on(document, 'uiForwardMail', this.forward); 94 | this.on(document, 'uiReplyToMail', this.reply); 95 | this.on(document, 'uiMailItemSelectionChanged', this.updateMailItemSelections); 96 | this.on(document, 'uiFolderSelectionChanged', this.updateFolderSelections); 97 | 98 | //the following bindings use delegation so that the event target is read at event time 99 | this.on(document, "click", { 100 | 'cancelSelector': this.cancel, 101 | 'sendSelector': this.requestSend, 102 | 'newControlSelector': this.newMail 103 | }); 104 | this.on('change', { 105 | 'recipientSelector': this.enableSend 106 | }); 107 | this.on('keydown', { 108 | 'hintSelector': this.removeHint 109 | }); 110 | }); 111 | } 112 | } 113 | ); 114 | -------------------------------------------------------------------------------- /app/component_ui/folders.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'flight/lib/component', 7 | './with_select' 8 | ], 9 | 10 | function(defineComponent, withSelect) { 11 | 12 | return defineComponent(folders, withSelect); 13 | 14 | function folders() { 15 | 16 | this.defaultAttrs({ 17 | selectedClass: 'selected', 18 | selectionChangedEvent: 'uiFolderSelectionChanged', 19 | 20 | //selectors 21 | itemSelector: 'li.folder-item', 22 | selectedItemSelector: 'li.folder-item.selected' 23 | }); 24 | 25 | this.fetchMailItems = function(ev, data) { 26 | this.trigger('uiMailItemsRequested', {folder: data.selectedIds[0]}); 27 | } 28 | 29 | this.after('initialize', function() { 30 | this.on('uiFolderSelectionChanged', this.fetchMailItems); 31 | }); 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /app/component_ui/mail_controls.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | [ 5 | 'flight/lib/component' 6 | ], 7 | 8 | function(defineComponent) { 9 | 10 | return defineComponent(mailControls); 11 | 12 | function mailControls() { 13 | this.defaultAttrs({ 14 | //selectors 15 | actionControlsSelector: 'button.mail-action', 16 | deleteControlSelector: '#delete_mail', 17 | moveControlSelector: '#move_mail', 18 | forwardControlSelector: '#forward', 19 | replyControlSelector: '#reply', 20 | singleItemActionSelector: 'button.single-item' 21 | }); 22 | 23 | this.disableAll = function() { 24 | this.select('actionControlsSelector').attr('disabled', 'disabled'); 25 | }; 26 | 27 | this.restyleOnSelectionChange = function(ev, data) { 28 | if (data.selectedIds.length > 1) { 29 | this.select('actionControlsSelector').not('button.single-item').removeAttr('disabled'); 30 | this.select('singleItemActionSelector').attr('disabled', 'disabled'); 31 | } else if (data.selectedIds.length == 1) { 32 | this.select('actionControlsSelector').removeAttr('disabled'); 33 | } else { 34 | this.disableAll(); 35 | } 36 | }; 37 | 38 | this.deleteMail = function(ev, data) { 39 | this.trigger('uiDeleteMail'); 40 | }; 41 | 42 | this.moveMail = function(ev, data) { 43 | this.trigger('uiMoveMail'); 44 | }; 45 | 46 | this.forwardMail = function(ev, data) { 47 | this.trigger('uiForwardMail'); 48 | }; 49 | 50 | this.replyToMail = function(ev, data) { 51 | this.trigger('uiReplyToMail'); 52 | }; 53 | 54 | this.after('initialize', function() { 55 | this.on('.mail-action', 'click', { 56 | 'deleteControlSelector': this.deleteMail, 57 | 'moveControlSelector': this.moveMail, 58 | 'forwardControlSelector': this.forwardMail, 59 | 'replyControlSelector': this.replyToMail 60 | }); 61 | this.on(document, 'uiMailItemSelectionChanged', this.restyleOnSelectionChange); 62 | this.on(document, 'uiFolderSelectionChanged', this.disableAll); 63 | }); 64 | } 65 | } 66 | ); 67 | 68 | -------------------------------------------------------------------------------- /app/component_ui/mail_items.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'flight/lib/component', 7 | './with_select' 8 | ], 9 | 10 | function(defineComponent, withSelect) { 11 | 12 | return defineComponent(mailItems, withSelect); 13 | 14 | function mailItems() { 15 | 16 | this.defaultAttrs({ 17 | deleteFolder: 'trash', 18 | selectedClass: 'selected', 19 | allowMultiSelect: true, 20 | selectionChangedEvent: 'uiMailItemSelectionChanged', 21 | selectedMailItems: [], 22 | selectedFolders: [], 23 | //selectors 24 | itemContainerSelector: null, 25 | itemSelector: 'tr.mail-item', 26 | selectedItemSelector: 'tr.mail-item.selected' 27 | }); 28 | 29 | this.renderItems = function(ev, data) { 30 | this.select('itemContainerSelector').html(data.markup); 31 | //new items, so no selections 32 | this.trigger('uiMailItemSelectionChanged', {selectedIds: []}); 33 | } 34 | 35 | this.updateMailItemSelections = function(ev, data) { 36 | this.attr.selectedMailItems = data.selectedIds; 37 | } 38 | 39 | this.updateFolderSelections = function(ev, data) { 40 | this.attr.selectedFolders = data.selectedIds; 41 | } 42 | 43 | this.requestDeletion = function() { 44 | this.trigger('uiMoveItemsRequested', { 45 | itemIds: this.attr.selectedMailItems, 46 | fromFolder: this.attr.selectedFolders[0], 47 | toFolder: this.attr.deleteFolder 48 | }); 49 | }; 50 | 51 | this.after('initialize', function() { 52 | this.on(document, 'dataMailItemsServed', this.renderItems); 53 | this.on(document, 'uiDeleteMail', this.requestDeletion); 54 | 55 | this.on('uiMailItemSelectionChanged', this.updateMailItemSelections); 56 | this.on(document, 'uiFolderSelectionChanged', this.updateFolderSelections); 57 | 58 | this.trigger('uiMailItemsRequested'); 59 | }); 60 | } 61 | } 62 | ); 63 | -------------------------------------------------------------------------------- /app/component_ui/move_to_selector.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | [ 6 | 'flight/lib/component', 7 | './with_select' 8 | ], 9 | 10 | function(defineComponent, withSelect) { 11 | 12 | return defineComponent(moveToSelector, withSelect); 13 | 14 | function moveToSelector() { 15 | 16 | this.defaultAttrs({ 17 | selectionChangedEvent: 'uiMoveToSelectionChanged', 18 | selectedMailItems: [], 19 | selectedFolders: [], 20 | //selectors 21 | itemSelector: 'li.move-to-item', 22 | selectedItemSelector: 'li.move-to-item.selected' 23 | }); 24 | 25 | this.requestSelectorWidget = function(ev, data) { 26 | this.trigger('uiAvailableFoldersRequested', { 27 | folder: this.attr.selectedFolders[0] 28 | }) 29 | }; 30 | 31 | this.launchSelector = function(ev, data) { 32 | var controlPosition = $(this.attr.moveActionSelector).offset(); 33 | this.$node.html(data.markup).show().css({ 34 | left: controlPosition.left, 35 | top: controlPosition.top + $(this.attr.moveActionSelector).outerHeight(), 36 | width: $(this.attr.moveActionSelector).outerWidth() 37 | }); 38 | window.setTimeout( 39 | (function() { 40 | this.on(document, 'click', this.hideSelector); 41 | }).bind(this), 0); 42 | }; 43 | 44 | this.hideSelector = function() { 45 | this.off(document, 'click', this.hideSelector); 46 | this.$node.hide(); 47 | } 48 | 49 | this.updateMailItemSelections = function(ev, data) { 50 | this.attr.selectedMailItems = data.selectedIds; 51 | } 52 | 53 | this.updateFolderSelections = function(ev, data) { 54 | this.attr.selectedFolders = data.selectedIds; 55 | } 56 | 57 | this.requestMoveTo = function(ev, data) { 58 | this.trigger('uiMoveItemsRequested', { 59 | itemIds: this.attr.selectedMailItems, 60 | fromFolder: this.attr.selectedFolders[0], 61 | toFolder: data.selectedIds[0] 62 | }); 63 | this.$node.hide(); 64 | }; 65 | 66 | this.after('initialize', function() { 67 | //show selector widget 68 | this.on(document, 'uiMoveMail', this.requestSelectorWidget); 69 | this.on(document, 'dataMoveToItemsServed', this.launchSelector); 70 | //listen for other selections 71 | this.on(document, 'uiMailItemSelectionChanged', this.updateMailItemSelections); 72 | this.on(document, 'uiFolderSelectionChanged', this.updateFolderSelections); 73 | //move items 74 | this.on('uiMoveToSelectionChanged', this.requestMoveTo); 75 | 76 | }); 77 | } 78 | } 79 | ); 80 | -------------------------------------------------------------------------------- /app/component_ui/with_select.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | 5 | function() { 6 | 7 | return withSelect; 8 | 9 | function withSelect() { 10 | 11 | this.defaultAttrs({ 12 | selectedIds: [] 13 | }); 14 | 15 | this.initializeSelections = function() { 16 | this.select('selectedItemSelector').toArray().forEach(function(el) { 17 | this.attr.selectedIds.push(el.getAttribute('id')); 18 | }, this); 19 | }; 20 | 21 | this.getSelectedIds = function() { 22 | return this.attr.selectedIds; 23 | }; 24 | 25 | this.toggleItemSelect = function(ev, data) { 26 | var $item = $(data.el), append; 27 | 28 | if ($item.hasClass(this.attr.selectedClass)) { 29 | this.unselectItem($item); 30 | } else { 31 | append = this.attr.allowMultiSelect && (ev.metaKey || ev.ctrlKey || ev.shiftKey); 32 | this.selectItem($item, append); 33 | } 34 | }; 35 | 36 | this.selectItem = function($item, append) { 37 | if (!append) { 38 | this.select('selectedItemSelector').removeClass(this.attr.selectedClass); 39 | this.attr.selectedIds = []; 40 | } 41 | $item.addClass(this.attr.selectedClass); 42 | 43 | this.attr.selectedIds.push($item.attr('id')); 44 | this.trigger(this.attr.selectionChangedEvent, {selectedIds: this.attr.selectedIds}); 45 | }; 46 | 47 | this.unselectItem = function($item) { 48 | $item.removeClass(this.attr.selectedClass); 49 | 50 | var thisIdIndex = this.attr.selectedIds.indexOf($item.attr('id')); 51 | this.attr.selectedIds.splice(thisIdIndex, 1); 52 | this.trigger(this.attr.selectionChangedEvent, {selectedIds: this.attr.selectedIds}); 53 | }; 54 | 55 | this.after("initialize", function() { 56 | this.on('click', { 57 | 'itemSelector': this.toggleItemSelect 58 | }); 59 | 60 | this.initializeSelections(); 61 | }); 62 | } 63 | } 64 | ); 65 | -------------------------------------------------------------------------------- /app/css/custom.css: -------------------------------------------------------------------------------- 1 | .modal.fade.in { 2 | left: 5%; 3 | top: 10%; 4 | margin: auto auto auto auto; 5 | } 6 | 7 | table { 8 | -webkit-touch-callout: none; 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | td.mailContact, 16 | span.mailSubject, 17 | span.mailMessage { 18 | font-size: 15px; 19 | } 20 | 21 | td.mailContact, 22 | span.mailSubject { 23 | font-weight: bold; 24 | } 25 | 26 | tr.mail-item.selected td, 27 | li.folder-item.selected, 28 | li.move-to-item.selected { 29 | background-color: #D9EDF7; 30 | } 31 | 32 | tr.mail-item.selected:hover td, 33 | li.folder-item.selected:hover, 34 | li.move-to-item.selected:hover { 35 | background-color: #C1E1FF; 36 | } 37 | 38 | li.folder-item:hover, 39 | li.move-to-item:hover { 40 | background-color: #EDEDED; 41 | } 42 | 43 | li.folder-item, 44 | li.move-to-item { 45 | padding: 3px 0; 46 | margin-left: -15px; 47 | font-size: 15px; 48 | cursor:pointer; 49 | } 50 | 51 | li.move-to-item { 52 | text-align: center; 53 | margin-right: -15px; 54 | } 55 | 56 | #new_mail { 57 | width: 100px; 58 | } 59 | 60 | div.compose-box { 61 | position: absolute; 62 | z-index: 10; 63 | background-color: #FFFFFF; 64 | width: 350px; 65 | border: 1px solid; 66 | } 67 | 68 | div.compose-body { 69 | padding: 0; 70 | } 71 | 72 | div.compose-header { 73 | font-size: 15px; 74 | } 75 | 76 | #recipient_select { 77 | width: 90%; 78 | margin-bottom: 0; 79 | font-weight: bold; 80 | } 81 | 82 | div.hint { 83 | border: 1px solid #EAEAEA; 84 | color:#CACACA; 85 | } 86 | 87 | #compose_message { 88 | height: 180px; 89 | } 90 | 91 | #compose_subject, 92 | #compose_message { 93 | font-size: 15px; 94 | padding: 15px; 95 | } 96 | 97 | #move_to_selector { 98 | position: absolute; 99 | z-index: 10; 100 | background-color: #FFFFFF; 101 | border: 1px solid; 102 | } -------------------------------------------------------------------------------- /app/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | function() { 5 | return { 6 | folders: ["inbox", "later", "sent", "trash"], 7 | contacts: [ 8 | { 9 | "id": "contact_342", 10 | "firstName": "Michael", 11 | "lastName": "Smith", 12 | "email": "ms@proxyweb.com" 13 | }, 14 | { 15 | "id": "contact_377", 16 | "firstName": "Mary", 17 | "lastName": "Jones", 18 | "email": "mary@jones.net" 19 | }, 20 | { 21 | "id": "contact_398", 22 | "firstName": "Billy", 23 | "lastName": "Idiot", 24 | "email": "william_idiot@gmail.com" 25 | } 26 | ], 27 | mail: [ 28 | { 29 | "id": "mail_2139", 30 | "contact_id": "contact_342", 31 | "folders": ["inbox"], 32 | "time": 1334891976104, 33 | "subject": "Consectetur adipiscing elit", 34 | "message": "Vestibulum vestibulum varius diam in iaculis. Praesent ultrices dui vitae nibh malesuada non iaculis ante vulputate. Suspendisse feugiat ultricies egestas. Aenean a odio libero. Quisque mollis leo et est euismod sit amet dignissim sapien venenatis. Morbi interdum adipiscing massa" 35 | }, 36 | { 37 | "id": "mail_2143", 38 | "contact_id": "contact_377", 39 | "folders": ["inbox", "later"], 40 | "important": "true", 41 | "time": 1334884976104, 42 | "subject": "Neque porro quisquam velit!!", 43 | "message": "Curabitur sollicitudin mi eget sapien posuere semper. Fusce at neque et lacus luctus vulputate vehicula ac enim" 44 | }, 45 | { 46 | "id": "mail_2154", 47 | "contact_id": "contact_398", 48 | "folders": ["inbox"], 49 | "important": "true", 50 | "unread": "true", 51 | "time": 1334874976199, 52 | "subject": "Proin egestas aliquam :)", 53 | "message": "Aenean nec erat id ipsum faucibus tristique. Nam blandit est lacinia turpis consectetur elementum. Nulla in risus ut sapien dignissim feugiat. Proin ultrices sodales imperdiet. Vestibulum vehicula blandit tincidunt. Vivamus posuere rhoncus orci, porta commodo mauris aliquam nec" 54 | }, 55 | { 56 | "id": "mail_2176", 57 | "contact_id": "contact_377", 58 | "folders": ["inbox"], 59 | "time": 1334884976104, 60 | "subject": "Sed ut perspiciatis unde omnis?", 61 | "message": "laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem." 62 | }, 63 | { 64 | "id": "mail_2191", 65 | "contact_id": "contact_398", 66 | "folders": ["inbox"], 67 | "unread": "true", 68 | "time": 1334874976199, 69 | "subject": "At vero eos et accusamus!", 70 | "message": "Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat" 71 | }, 72 | { 73 | "id": "mail_2203", 74 | "contact_id": "contact_377", 75 | "folders": ["later"], 76 | "important": "true", 77 | "time": 1334874576199, 78 | "subject": "Mi netus convallis", 79 | "message": "Egestas morbi at. Curabitur aliquet et commodo nonummy, aliquam quis arcu, sed pellentesque vitae molestie mattis magna, in eget, risus nulla vivamus vulputate" 80 | }, 81 | { 82 | "id": "mail_2212", 83 | "contact_id": "contact_398", 84 | "folders": ["sent"], 85 | "time": 1334874579867, 86 | "subject": "Fusce tristique pretium eros a gravida", 87 | "message": "Proin malesuada" 88 | }, 89 | { 90 | "id": "mail_2021", 91 | "contact_id": "contact_342", 92 | "folders": ["trash"], 93 | "time": 1134874579824, 94 | "subject": "Phasellus vitae interdum nulla.", 95 | "message": "Pellentesque quam eros, mollis quis vulputate eget, pellentesque nec ipsum. Cras dignissim fringilla ligula, ac ullamcorper dui convallis blandit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam id nunc ac orci hendrerit faucibus vel in ante. Mauris nec est turpis, ut fringilla mi. Suspendisse vel tortor at nulla facilisis venenatis in sit amet ligula." 96 | }, 97 | { 98 | "id": "mail_1976", 99 | "contact_id": "contact_377", 100 | "folders": ["trash"], 101 | "time": 1034874579813, 102 | "subject": "Fusce tristique pretium :(", 103 | "message": "aliquam quis arcu." 104 | } 105 | ] 106 | }; 107 | return data; 108 | } 109 | ); 110 | 111 | -------------------------------------------------------------------------------- /app/templates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define( 4 | function() { 5 | var mailItem = 6 | '{{#mailItems}}\ 7 | \ 8 | {{#important}}\ 9 | Important\ 10 | {{/important}}\ 11 | {{^important}}\ 12 |  \ 13 | {{/important}}\ 14 | {{name}}\ 15 | \ 16 | \ 17 | {{formattedSubject}}\ 18 | \ 19 | \ 20 | - {{formattedMessage}}\ 21 | \ 22 | \ 23 | \ 24 | {{/mailItems}}'; 25 | 26 | var composeBox = 27 | '\ 35 | \ 43 | '; 47 | 48 | var moveToSelector = 49 | ''; 54 | 55 | return { 56 | mailItem: mailItem, 57 | composeBox: composeBox, 58 | moveToSelector: moveToSelector 59 | } 60 | } 61 | 62 | ); 63 | -------------------------------------------------------------------------------- /bower_components/bootstrap/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /bower_components/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flightjs/example-app/56230f33fd4a7c726477d1c8fcda5acb370a7367/bower_components/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /bower_components/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flightjs/example-app/56230f33fd4a7c726477d1c8fcda5acb370a7367/bower_components/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /bower_components/es5-shim/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /bower_components/es5-shim/CHANGES: -------------------------------------------------------------------------------- 1 | 2 | 2.0.0 3 | - Separate reliable shims from dubious shims (shams). 4 | 5 | 1.2.10 6 | - Group-effort Style Cleanup 7 | - Took a stab at fixing Object.defineProperty on IE8 without 8 | bad side-effects. (@hax) 9 | - Object.isExtensible no longer fakes it. (@xavierm) 10 | - Date.prototype.toISOString no longer deals with partial 11 | ISO dates, per spec (@kitcambridge) 12 | - More (mostly from @bryanforbes) 13 | 14 | 1.2.9 15 | - Corrections to toISOString by @kitcambridge 16 | - Fixed three bugs in array methods revealed by Jasmine tests. 17 | - Cleaned up Function.prototype.bind with more fixes and tests from 18 | @bryanforbes. 19 | 20 | 1.2.8 21 | - Actually fixed problems with Function.prototype.bind, and regressions 22 | from 1.2.7 (@bryanforbes, @jdalton #36) 23 | 24 | 1.2.7 - REGRESSED 25 | - Fixed problems with Function.prototype.bind when called as a constructor. 26 | (@jdalton #36) 27 | 28 | 1.2.6 29 | - Revised Date.parse to match ES 5.1 (kitcambridge) 30 | 31 | 1.2.5 32 | - Fixed a bug for padding it Date..toISOString (tadfisher issue #33) 33 | 34 | 1.2.4 35 | - Fixed a descriptor bug in Object.defineProperty (raynos) 36 | 37 | 1.2.3 38 | - Cleaned up RequireJS and 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /bower_components/es5-shim/tests/lib/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.TrivialReporter = function(doc) { 2 | this.document = doc || document; 3 | this.suiteDivs = {}; 4 | this.logRunningSpecs = false; 5 | }; 6 | 7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 8 | var el = document.createElement(type); 9 | 10 | for (var i = 2; i < arguments.length; i++) { 11 | var child = arguments[i]; 12 | 13 | if (typeof child === 'string') { 14 | el.appendChild(document.createTextNode(child)); 15 | } else { 16 | if (child) { el.appendChild(child); } 17 | } 18 | } 19 | 20 | for (var attr in attrs) { 21 | if (attr == "className") { 22 | el[attr] = attrs[attr]; 23 | } else { 24 | el.setAttribute(attr, attrs[attr]); 25 | } 26 | } 27 | 28 | return el; 29 | }; 30 | 31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 32 | var showPassed, showSkipped; 33 | 34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 35 | this.createDom('div', { className: 'banner' }, 36 | this.createDom('div', { className: 'logo' }, 37 | this.createDom('span', { className: 'title' }, "Jasmine"), 38 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 39 | this.createDom('div', { className: 'options' }, 40 | "Show ", 41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 45 | ) 46 | ), 47 | 48 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 52 | ); 53 | 54 | this.document.body.appendChild(this.outerDiv); 55 | 56 | var suites = runner.suites(); 57 | for (var i = 0; i < suites.length; i++) { 58 | var suite = suites[i]; 59 | var suiteDiv = this.createDom('div', { className: 'suite' }, 60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 62 | this.suiteDivs[suite.id] = suiteDiv; 63 | var parentDiv = this.outerDiv; 64 | if (suite.parentSuite) { 65 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 66 | } 67 | parentDiv.appendChild(suiteDiv); 68 | } 69 | 70 | this.startedAt = new Date(); 71 | 72 | var self = this; 73 | showPassed.onclick = function(evt) { 74 | if (showPassed.checked) { 75 | self.outerDiv.className += ' show-passed'; 76 | } else { 77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 78 | } 79 | }; 80 | 81 | showSkipped.onclick = function(evt) { 82 | if (showSkipped.checked) { 83 | self.outerDiv.className += ' show-skipped'; 84 | } else { 85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 86 | } 87 | }; 88 | }; 89 | 90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 91 | var results = runner.results(); 92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 93 | this.runnerDiv.setAttribute("class", className); 94 | //do it twice for IE 95 | this.runnerDiv.setAttribute("className", className); 96 | var specs = runner.specs(); 97 | var specCount = 0; 98 | for (var i = 0; i < specs.length; i++) { 99 | if (this.specFilter(specs[i])) { 100 | specCount++; 101 | } 102 | } 103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 106 | 107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 108 | }; 109 | 110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 111 | var results = suite.results(); 112 | var status = results.passed() ? 'passed' : 'failed'; 113 | if (results.totalCount === 0) { // todo: change this to check results.skipped 114 | status = 'skipped'; 115 | } 116 | this.suiteDivs[suite.id].className += " " + status; 117 | }; 118 | 119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 120 | if (this.logRunningSpecs) { 121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 122 | } 123 | }; 124 | 125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 126 | var results = spec.results(); 127 | var status = results.passed() ? 'passed' : 'failed'; 128 | if (results.skipped) { 129 | status = 'skipped'; 130 | } 131 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 133 | this.createDom('a', { 134 | className: 'description', 135 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 136 | title: spec.getFullName() 137 | }, spec.description)); 138 | 139 | 140 | var resultItems = results.getItems(); 141 | var messagesDiv = this.createDom('div', { className: 'messages' }); 142 | for (var i = 0; i < resultItems.length; i++) { 143 | var result = resultItems[i]; 144 | 145 | if (result.type == 'log') { 146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 147 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 149 | 150 | if (result.trace.stack) { 151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 152 | } 153 | } 154 | } 155 | 156 | if (messagesDiv.childNodes.length > 0) { 157 | specDiv.appendChild(messagesDiv); 158 | } 159 | 160 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 161 | }; 162 | 163 | jasmine.TrivialReporter.prototype.log = function() { 164 | var console = jasmine.getGlobal().console; 165 | if (console && console.log) { 166 | if (console.log.apply) { 167 | console.log.apply(console, arguments); 168 | } else { 169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 170 | } 171 | } 172 | }; 173 | 174 | jasmine.TrivialReporter.prototype.getLocation = function() { 175 | return this.document.location; 176 | }; 177 | 178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 179 | var paramMap = {}; 180 | var params = this.getLocation().search.substring(1).split('&'); 181 | for (var i = 0; i < params.length; i++) { 182 | var p = params[i].split('='); 183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 184 | } 185 | 186 | if (!paramMap.spec) { 187 | return true; 188 | } 189 | return spec.getFullName().indexOf(paramMap.spec) === 0; 190 | }; 191 | -------------------------------------------------------------------------------- /bower_components/es5-shim/tests/lib/jasmine.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 3 | } 4 | 5 | 6 | .jasmine_reporter a:visited, .jasmine_reporter a { 7 | color: #303; 8 | } 9 | 10 | .jasmine_reporter a:hover, .jasmine_reporter a:active { 11 | color: blue; 12 | } 13 | 14 | .run_spec { 15 | float:right; 16 | padding-right: 5px; 17 | font-size: .8em; 18 | text-decoration: none; 19 | } 20 | 21 | .jasmine_reporter { 22 | margin: 0 5px; 23 | } 24 | 25 | .banner { 26 | color: #303; 27 | background-color: #fef; 28 | padding: 5px; 29 | } 30 | 31 | .logo { 32 | float: left; 33 | font-size: 1.1em; 34 | padding-left: 5px; 35 | } 36 | 37 | .logo .version { 38 | font-size: .6em; 39 | padding-left: 1em; 40 | } 41 | 42 | .runner.running { 43 | background-color: yellow; 44 | } 45 | 46 | 47 | .options { 48 | text-align: right; 49 | font-size: .8em; 50 | } 51 | 52 | 53 | 54 | 55 | .suite { 56 | border: 1px outset gray; 57 | margin: 5px 0; 58 | padding-left: 1em; 59 | } 60 | 61 | .suite .suite { 62 | margin: 5px; 63 | } 64 | 65 | .suite.passed { 66 | background-color: #dfd; 67 | } 68 | 69 | .suite.failed { 70 | background-color: #fdd; 71 | } 72 | 73 | .spec { 74 | margin: 5px; 75 | padding-left: 1em; 76 | clear: both; 77 | } 78 | 79 | .spec.failed, .spec.passed, .spec.skipped { 80 | padding-bottom: 5px; 81 | border: 1px solid gray; 82 | } 83 | 84 | .spec.failed { 85 | background-color: #fbb; 86 | border-color: red; 87 | } 88 | 89 | .spec.passed { 90 | background-color: #bfb; 91 | border-color: green; 92 | } 93 | 94 | .spec.skipped { 95 | background-color: #bbb; 96 | } 97 | 98 | .messages { 99 | border-left: 1px dashed gray; 100 | padding-left: 1em; 101 | padding-right: 1em; 102 | } 103 | 104 | .passed { 105 | background-color: #cfc; 106 | display: none; 107 | } 108 | 109 | .failed { 110 | background-color: #fbb; 111 | } 112 | 113 | .skipped { 114 | color: #777; 115 | background-color: #eee; 116 | display: none; 117 | } 118 | 119 | 120 | /*.resultMessage {*/ 121 | /*white-space: pre;*/ 122 | /*}*/ 123 | 124 | .resultMessage span.result { 125 | display: block; 126 | line-height: 2em; 127 | color: black; 128 | } 129 | 130 | .resultMessage .mismatch { 131 | color: black; 132 | } 133 | 134 | .stackTrace { 135 | white-space: pre; 136 | font-size: .8em; 137 | margin-left: 10px; 138 | max-height: 5em; 139 | overflow: auto; 140 | border: 1px inset red; 141 | padding: 1em; 142 | background: #eef; 143 | } 144 | 145 | .finished-at { 146 | padding-left: 1em; 147 | font-size: .6em; 148 | } 149 | 150 | .show-passed .passed, 151 | .show-skipped .skipped { 152 | display: block; 153 | } 154 | 155 | 156 | #jasmine_content { 157 | position:fixed; 158 | right: 100%; 159 | } 160 | 161 | .runner { 162 | border: 1px solid gray; 163 | display: block; 164 | margin: 5px 0; 165 | padding: 2px 0 2px 10px; 166 | } 167 | -------------------------------------------------------------------------------- /bower_components/es5-shim/tests/lib/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flightjs/example-app/56230f33fd4a7c726477d1c8fcda5acb370a7367/bower_components/es5-shim/tests/lib/jasmine_favicon.png -------------------------------------------------------------------------------- /bower_components/es5-shim/tests/spec/s-date.js: -------------------------------------------------------------------------------- 1 | describe('Date', function () { 2 | 3 | describe('now', function () { 4 | it('should be the current time', function () { 5 | expect(Date.now() === new Date().getTime()).toBe(true); 6 | }); 7 | }); 8 | 9 | describe("parse", function () { 10 | // TODO: Write the rest of the test. 11 | 12 | it('should support extended years', function () { 13 | 14 | expect(Date.parse('0001-01-01T00:00:00Z')).toBe(-62135596800000); 15 | expect(Date.parse('+275760-09-13T00:00:00.000Z')).toBe(8.64e15); 16 | expect(Date.parse('+033658-09-27T01:46:40.000Z')).toBe(1e15); 17 | expect(Date.parse('-000001-01-01T00:00:00Z')).toBe(-62198755200000); 18 | expect(Date.parse('+002009-12-15T00:00:00Z')).toBe(1260835200000); 19 | 20 | }); 21 | 22 | }); 23 | 24 | describe("toISOString", function () { 25 | // TODO: write the rest of the test. 26 | 27 | it('should support extended years', function () { 28 | expect(new Date(-62198755200000).toISOString().indexOf('-000001-01-01')).toBe(0); 29 | expect(new Date(8.64e15).toISOString().indexOf('+275760-09-13')).toBe(0); 30 | }); 31 | }); 32 | 33 | describe("toJSON", function () { 34 | it('should return the isoString when stringified', function () { 35 | var date = new Date(); 36 | expect(JSON.stringify(date.toISOString())).toBe(JSON.stringify(date)); 37 | }) 38 | }); 39 | 40 | }); -------------------------------------------------------------------------------- /bower_components/es5-shim/tests/spec/s-function.js: -------------------------------------------------------------------------------- 1 | 2 | describe('Function', function() { 3 | "use strict"; 4 | describe('bind', function() { 5 | var actual, expected, 6 | testSubject; 7 | 8 | testSubject = { 9 | push: function(o) { 10 | this.a.push(o); 11 | } 12 | }; 13 | 14 | function func() { 15 | Array.prototype.forEach.call(arguments, function(a) { 16 | this.push(a); 17 | }, this); 18 | return this; 19 | }; 20 | 21 | beforeEach(function() { 22 | actual = []; 23 | testSubject.a = []; 24 | }); 25 | 26 | it('binds properly without a context', function() { 27 | var context; 28 | testSubject.func = function() { 29 | context = this; 30 | }.bind(); 31 | testSubject.func(); 32 | expect(context).toBe(function() {return this}.call()); 33 | }); 34 | it('binds properly without a context, and still supplies bound arguments', function() { 35 | var a, context; 36 | testSubject.func = function() { 37 | a = Array.prototype.slice.call(arguments); 38 | context = this; 39 | }.bind(undefined, 1,2,3); 40 | testSubject.func(1,2,3); 41 | expect(a).toEqual([1,2,3,1,2,3]); 42 | expect(context).toBe(function() {return this}.call()); 43 | }); 44 | it('binds a context properly', function() { 45 | testSubject.func = func.bind(actual); 46 | testSubject.func(1,2,3); 47 | expect(actual).toEqual([1,2,3]); 48 | expect(testSubject.a).toEqual([]); 49 | }); 50 | it('binds a context and supplies bound arguments', function() { 51 | testSubject.func = func.bind(actual, 1,2,3); 52 | testSubject.func(4,5,6); 53 | expect(actual).toEqual([1,2,3,4,5,6]); 54 | expect(testSubject.a).toEqual([]); 55 | }); 56 | 57 | it('returns properly without binding a context', function() { 58 | testSubject.func = function() { 59 | return this; 60 | }.bind(); 61 | var context = testSubject.func(); 62 | expect(context).toBe(function() {return this}.call()); 63 | }); 64 | it('returns properly without binding a context, and still supplies bound arguments', function() { 65 | var context; 66 | testSubject.func = function() { 67 | context = this; 68 | return Array.prototype.slice.call(arguments); 69 | }.bind(undefined, 1,2,3); 70 | actual = testSubject.func(1,2,3); 71 | expect(context).toBe(function() {return this}.call()); 72 | expect(actual).toEqual([1,2,3,1,2,3]); 73 | }); 74 | it('returns properly while binding a context properly', function() { 75 | var ret; 76 | testSubject.func = func.bind(actual); 77 | ret = testSubject.func(1,2,3); 78 | expect(ret).toBe(actual); 79 | expect(ret).not.toBe(testSubject); 80 | }); 81 | it('returns properly while binding a context and supplies bound arguments', function() { 82 | var ret; 83 | testSubject.func = func.bind(actual, 1,2,3); 84 | ret = testSubject.func(4,5,6); 85 | expect(ret).toBe(actual); 86 | expect(ret).not.toBe(testSubject); 87 | }); 88 | it('passes the correct arguments as a constructor', function() { 89 | var ret, expected = { name: "Correct" }; 90 | testSubject.func = function(arg) { 91 | return arg; 92 | }.bind({ name: "Incorrect" }); 93 | ret = new testSubject.func(expected); 94 | expect(ret).toBe(expected); 95 | }); 96 | it('returns the return value of the bound function when called as a constructor', function () { 97 | var oracle = [1, 2, 3]; 98 | var subject = function () { 99 | return oracle; 100 | }.bind(null); 101 | var result = new subject; 102 | expect(result).toBe(oracle); 103 | }); 104 | it('returns the correct value if constructor returns primitive', function() { 105 | var oracle = [1, 2, 3]; 106 | var subject = function () { 107 | return oracle; 108 | }.bind(null); 109 | var result = new subject; 110 | expect(result).toBe(oracle); 111 | 112 | oracle = {}; 113 | result = new subject; 114 | expect(result).toBe(oracle); 115 | 116 | oracle = function(){}; 117 | result = new subject; 118 | expect(result).toBe(oracle); 119 | 120 | oracle = "asdf"; 121 | result = new subject; 122 | expect(result).not.toBe(oracle); 123 | 124 | oracle = null; 125 | result = new subject; 126 | expect(result).not.toBe(oracle); 127 | 128 | oracle = true; 129 | result = new subject; 130 | expect(result).not.toBe(oracle); 131 | 132 | oracle = 1; 133 | result = new subject; 134 | expect(result).not.toBe(oracle); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /bower_components/es5-shim/tests/spec/s-object.js: -------------------------------------------------------------------------------- 1 | describe('Object', function () { 2 | "use strict"; 3 | 4 | describe("Object.keys", function () { 5 | var obj = { 6 | "str": "boz", 7 | "obj": { }, 8 | "arr": [], 9 | "bool": true, 10 | "num": 42, 11 | "null": null, 12 | "undefined": undefined 13 | }; 14 | 15 | var loopedValues = []; 16 | for (var k in obj) { 17 | loopedValues.push(k); 18 | } 19 | 20 | var keys = Object.keys(obj); 21 | it('should have correct length', function () { 22 | expect(keys.length).toBe(7); 23 | }); 24 | 25 | it('should return an Array', function () { 26 | expect(Array.isArray(keys)).toBe(true); 27 | }); 28 | 29 | it('should return names which are own properties', function () { 30 | keys.forEach(function (name) { 31 | expect(obj.hasOwnProperty(name)).toBe(true); 32 | }); 33 | }); 34 | 35 | it('should return names which are enumerable', function () { 36 | keys.forEach(function (name) { 37 | expect(loopedValues.indexOf(name)).toNotBe(-1); 38 | }) 39 | }); 40 | 41 | it('should throw error for non object', function () { 42 | var e = {}; 43 | expect(function () { 44 | try { 45 | Object.keys(42) 46 | } catch (err) { 47 | throw e; 48 | } 49 | }).toThrow(e); 50 | }); 51 | }); 52 | 53 | describe("Object.isExtensible", function () { 54 | var obj = { }; 55 | 56 | it('should return true if object is extensible', function () { 57 | expect(Object.isExtensible(obj)).toBe(true); 58 | }); 59 | 60 | it('should return false if object is not extensible', function () { 61 | expect(Object.isExtensible(Object.preventExtensions(obj))).toBe(false); 62 | }); 63 | 64 | it('should return false if object is seal', function () { 65 | expect(Object.isExtensible(Object.seal(obj))).toBe(false); 66 | }); 67 | 68 | it('should return false if object is freeze', function () { 69 | expect(Object.isExtensible(Object.freeze(obj))).toBe(false); 70 | }); 71 | 72 | it('should throw error for non object', function () { 73 | var e1 = {}; 74 | expect(function () { 75 | try { 76 | Object.isExtensible(42) 77 | } catch (err) { 78 | throw e1; 79 | } 80 | }).toThrow(e1); 81 | }); 82 | }); 83 | 84 | }); -------------------------------------------------------------------------------- /bower_components/es5-shim/tests/spec/s-string.js: -------------------------------------------------------------------------------- 1 | describe('String', function() { 2 | "use strict"; 3 | describe("trim", function() { 4 | var test = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFFHello, World!\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF"; 5 | 6 | it('trims all ES5 whitespace', function() { 7 | expect(test.trim()).toEqual("Hello, World!"); 8 | expect(test.trim().length).toEqual(13); 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /bower_components/flight/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | before_script: 5 | - "export DISPLAY=:99.0" 6 | - "sh -e /etc/init.d/xvfb start" 7 | script: "npm install; make standalone; phantomjs test/phantom-jasmine/run_jasmine_test.coffee test/run/jasmine_test.html" 8 | -------------------------------------------------------------------------------- /bower_components/flight/lib/advice.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [ 10 | './compose' 11 | ], 12 | 13 | function(compose) { 14 | 'use strict'; 15 | 16 | var advice = { 17 | 18 | around: function(base, wrapped) { 19 | return function composedAround() { 20 | // unpacking arguments by hand benchmarked faster 21 | var i = 0, l = arguments.length, args = new Array(l + 1); 22 | args[0] = base.bind(this); 23 | for (; i < l; i++) args[i + 1] = arguments[i]; 24 | 25 | return wrapped.apply(this, args); 26 | }; 27 | }, 28 | 29 | before: function(base, before) { 30 | var beforeFn = (typeof before == 'function') ? before : before.obj[before.fnName]; 31 | return function composedBefore() { 32 | beforeFn.apply(this, arguments); 33 | return base.apply(this, arguments); 34 | }; 35 | }, 36 | 37 | after: function(base, after) { 38 | var afterFn = (typeof after == 'function') ? after : after.obj[after.fnName]; 39 | return function composedAfter() { 40 | var res = (base.unbound || base).apply(this, arguments); 41 | afterFn.apply(this, arguments); 42 | return res; 43 | }; 44 | }, 45 | 46 | // a mixin that allows other mixins to augment existing functions by adding additional 47 | // code before, after or around. 48 | withAdvice: function() { 49 | ['before', 'after', 'around'].forEach(function(m) { 50 | this[m] = function(method, fn) { 51 | 52 | compose.unlockProperty(this, method, function() { 53 | if (typeof this[method] == 'function') { 54 | this[method] = advice[m](this[method], fn); 55 | } else { 56 | this[method] = fn; 57 | } 58 | 59 | return this[method]; 60 | }); 61 | 62 | }; 63 | }, this); 64 | } 65 | }; 66 | 67 | return advice; 68 | } 69 | ); 70 | -------------------------------------------------------------------------------- /bower_components/flight/lib/base.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [ 10 | './utils', 11 | './registry', 12 | './debug' 13 | ], 14 | 15 | function(utils, registry, debug) { 16 | 'use strict'; 17 | 18 | // common mixin allocates basic functionality - used by all component prototypes 19 | // callback context is bound to component 20 | var componentId = 0; 21 | 22 | function teardownInstance(instanceInfo){ 23 | instanceInfo.events.slice().forEach(function(event) { 24 | var args = [event.type]; 25 | 26 | event.element && args.unshift(event.element); 27 | (typeof event.callback == 'function') && args.push(event.callback); 28 | 29 | this.off.apply(this, args); 30 | }, instanceInfo.instance); 31 | } 32 | 33 | function checkSerializable(type, data) { 34 | try { 35 | window.postMessage(data, '*'); 36 | } catch(e) { 37 | console.log('unserializable data for event',type,':',data); 38 | throw new Error( 39 | ['The event', type, 'on component', this.toString(), 'was triggered with non-serializable data'].join(' ') 40 | ); 41 | } 42 | } 43 | 44 | function withBase() { 45 | 46 | // delegate trigger, bind and unbind to an element 47 | // if $element not supplied, use component's node 48 | // other arguments are passed on 49 | // event can be either a string specifying the type 50 | // of the event, or a hash specifying both the type 51 | // and a default function to be called. 52 | this.trigger = function() { 53 | var $element, type, data, event, defaultFn; 54 | var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex]; 55 | 56 | if (typeof lastArg != 'string' && !(lastArg && lastArg.defaultBehavior)) { 57 | lastIndex--; 58 | data = lastArg; 59 | } 60 | 61 | if (lastIndex == 1) { 62 | $element = $(arguments[0]); 63 | event = arguments[1]; 64 | } else { 65 | $element = this.$node; 66 | event = arguments[0]; 67 | } 68 | 69 | if (event.defaultBehavior) { 70 | defaultFn = event.defaultBehavior; 71 | event = $.Event(event.type); 72 | } 73 | 74 | type = event.type || event; 75 | 76 | if (debug.enabled && window.postMessage) { 77 | checkSerializable.call(this, type, data); 78 | } 79 | 80 | if (typeof this.attr.eventData === 'object') { 81 | data = $.extend(true, {}, this.attr.eventData, data); 82 | } 83 | 84 | $element.trigger((event || type), data); 85 | 86 | if (defaultFn && !event.isDefaultPrevented()) { 87 | (this[defaultFn] || defaultFn).call(this); 88 | } 89 | 90 | return $element; 91 | }; 92 | 93 | this.on = function() { 94 | var $element, type, callback, originalCb; 95 | var lastIndex = arguments.length - 1, origin = arguments[lastIndex]; 96 | 97 | if (typeof origin == 'object') { 98 | //delegate callback 99 | originalCb = utils.delegate( 100 | this.resolveDelegateRules(origin) 101 | ); 102 | } else { 103 | originalCb = origin; 104 | } 105 | 106 | if (lastIndex == 2) { 107 | $element = $(arguments[0]); 108 | type = arguments[1]; 109 | } else { 110 | $element = this.$node; 111 | type = arguments[0]; 112 | } 113 | 114 | if (typeof originalCb != 'function' && typeof originalCb != 'object') { 115 | throw new Error('Unable to bind to "' + type + '" because the given callback is not a function or an object'); 116 | } 117 | 118 | callback = originalCb.bind(this); 119 | callback.target = originalCb; 120 | callback.context = this; 121 | 122 | $element.on(type, callback); 123 | 124 | // store every bound version of the callback 125 | originalCb.bound || (originalCb.bound = []); 126 | originalCb.bound.push(callback); 127 | 128 | return callback; 129 | }; 130 | 131 | this.off = function() { 132 | var $element, type, callback; 133 | var lastIndex = arguments.length - 1; 134 | 135 | if (typeof arguments[lastIndex] == 'function') { 136 | callback = arguments[lastIndex]; 137 | lastIndex -= 1; 138 | } 139 | 140 | if (lastIndex == 1) { 141 | $element = $(arguments[0]); 142 | type = arguments[1]; 143 | } else { 144 | $element = this.$node; 145 | type = arguments[0]; 146 | } 147 | 148 | if (callback) { 149 | //set callback to version bound against this instance 150 | callback.bound && callback.bound.some(function(fn, i, arr) { 151 | if (fn.context && (this.identity == fn.context.identity)) { 152 | arr.splice(i, 1); 153 | callback = fn; 154 | return true; 155 | } 156 | }, this); 157 | } 158 | 159 | return $element.off(type, callback); 160 | }; 161 | 162 | this.resolveDelegateRules = function(ruleInfo) { 163 | var rules = {}; 164 | 165 | Object.keys(ruleInfo).forEach(function(r) { 166 | if (!(r in this.attr)) { 167 | throw new Error('Component "' + this.toString() + '" wants to listen on "' + r + '" but no such attribute was defined.'); 168 | } 169 | rules[this.attr[r]] = ruleInfo[r]; 170 | }, this); 171 | 172 | return rules; 173 | }; 174 | 175 | this.defaultAttrs = function(defaults) { 176 | utils.push(this.defaults, defaults, true) || (this.defaults = defaults); 177 | }; 178 | 179 | this.select = function(attributeKey) { 180 | return this.$node.find(this.attr[attributeKey]); 181 | }; 182 | 183 | this.initialize = function(node, attrs) { 184 | attrs || (attrs = {}); 185 | //only assign identity if there isn't one (initialize can be called multiple times) 186 | this.identity || (this.identity = componentId++); 187 | 188 | if (!node) { 189 | throw new Error('Component needs a node'); 190 | } 191 | 192 | if (node.jquery) { 193 | this.node = node[0]; 194 | this.$node = node; 195 | } else { 196 | this.node = node; 197 | this.$node = $(node); 198 | } 199 | 200 | // merge defaults with supplied options 201 | // put options in attr.__proto__ to avoid merge overhead 202 | var attr = Object.create(attrs); 203 | for (var key in this.defaults) { 204 | if (!attrs.hasOwnProperty(key)) { 205 | attr[key] = this.defaults[key]; 206 | } 207 | } 208 | 209 | this.attr = attr; 210 | 211 | Object.keys(this.defaults || {}).forEach(function(key) { 212 | if (this.defaults[key] === null && this.attr[key] === null) { 213 | throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.toString() + '".'); 214 | } 215 | }, this); 216 | 217 | return this; 218 | }; 219 | 220 | this.teardown = function() { 221 | teardownInstance(registry.findInstanceInfo(this)); 222 | }; 223 | } 224 | 225 | return withBase; 226 | } 227 | ); 228 | -------------------------------------------------------------------------------- /bower_components/flight/lib/component.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [ 10 | './advice', 11 | './utils', 12 | './compose', 13 | './base', 14 | './registry', 15 | './logger', 16 | './debug' 17 | ], 18 | 19 | function(advice, utils, compose, withBase, registry, withLogging, debug) { 20 | 'use strict'; 21 | 22 | var functionNameRegEx = /function (.*?)\s?\(/; 23 | 24 | // teardown for all instances of this constructor 25 | function teardownAll() { 26 | var componentInfo = registry.findComponentInfo(this); 27 | 28 | componentInfo && Object.keys(componentInfo.instances).forEach(function(k) { 29 | var info = componentInfo.instances[k]; 30 | // It's possible that a previous teardown caused another component to teardown, 31 | // so we can't assume that the instances object is as it was. 32 | if (info && info.instance) { 33 | info.instance.teardown(); 34 | } 35 | }); 36 | } 37 | 38 | function checkSerializable(type, data) { 39 | try { 40 | window.postMessage(data, '*'); 41 | } catch(e) { 42 | console.log('unserializable data for event',type,':',data); 43 | throw new Error( 44 | ['The event', type, 'on component', this.toString(), 'was triggered with non-serializable data'].join(' ') 45 | ); 46 | } 47 | } 48 | 49 | function attachTo(selector/*, options args */) { 50 | // unpacking arguments by hand benchmarked faster 51 | var l = arguments.length; 52 | var args = new Array(l - 1); 53 | for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; 54 | 55 | if (!selector) { 56 | throw new Error('Component needs to be attachTo\'d a jQuery object, native node or selector string'); 57 | } 58 | 59 | var options = utils.merge.apply(utils, args); 60 | var componentInfo = registry.findComponentInfo(this); 61 | 62 | $(selector).each(function(i, node) { 63 | if (componentInfo && componentInfo.isAttachedTo(node)) { 64 | // already attached 65 | return; 66 | } 67 | 68 | (new this).initialize(node, options); 69 | }.bind(this)); 70 | } 71 | 72 | // define the constructor for a custom component type 73 | // takes an unlimited number of mixin functions as arguments 74 | // typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability); 75 | function define(/*mixins*/) { 76 | // unpacking arguments by hand benchmarked faster 77 | var l = arguments.length; 78 | // add three for common mixins 79 | var mixins = new Array(l + 3); 80 | for (var i = 0; i < l; i++) mixins[i] = arguments[i]; 81 | 82 | var Component = function() {}; 83 | 84 | Component.toString = Component.prototype.toString = function() { 85 | var prettyPrintMixins = mixins.map(function(mixin) { 86 | if (mixin.name == null) { 87 | // function name property not supported by this browser, use regex 88 | var m = mixin.toString().match(functionNameRegEx); 89 | return (m && m[1]) ? m[1] : ''; 90 | } else { 91 | return (mixin.name != 'withBase') ? mixin.name : ''; 92 | } 93 | }).filter(Boolean).join(', '); 94 | return prettyPrintMixins; 95 | }; 96 | 97 | if (debug.enabled) { 98 | Component.describe = Component.prototype.describe = Component.toString(); 99 | } 100 | 101 | // 'options' is optional hash to be merged with 'defaults' in the component definition 102 | Component.attachTo = attachTo; 103 | Component.teardownAll = teardownAll; 104 | 105 | // prepend common mixins to supplied list, then mixin all flavors 106 | if (debug.enabled) { 107 | mixins.unshift(withLogging); 108 | } 109 | mixins.unshift(withBase, advice.withAdvice, registry.withRegistration); 110 | compose.mixin(Component.prototype, mixins); 111 | 112 | return Component; 113 | } 114 | 115 | define.teardownAll = function() { 116 | registry.components.slice().forEach(function(c) { 117 | c.component.teardownAll(); 118 | }); 119 | registry.reset(); 120 | }; 121 | 122 | return define; 123 | } 124 | ); 125 | -------------------------------------------------------------------------------- /bower_components/flight/lib/compose.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [ 10 | './utils', 11 | './debug' 12 | ], 13 | 14 | function(utils, debug) { 15 | 'use strict'; 16 | 17 | //enumerables are shims - getOwnPropertyDescriptor shim doesn't work 18 | var canWriteProtect = debug.enabled && !utils.isEnumerable(Object, 'getOwnPropertyDescriptor'); 19 | //whitelist of unlockable property names 20 | var dontLock = ['mixedIn']; 21 | 22 | if (canWriteProtect) { 23 | //IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects 24 | try { 25 | Object.getOwnPropertyDescriptor(Object, 'keys'); 26 | } catch(e) { 27 | canWriteProtect = false; 28 | } 29 | } 30 | 31 | function setPropertyWritability(obj, isWritable) { 32 | if (!canWriteProtect) { 33 | return; 34 | } 35 | 36 | var props = Object.create(null); 37 | 38 | Object.keys(obj).forEach( 39 | function (key) { 40 | if (dontLock.indexOf(key) < 0) { 41 | var desc = Object.getOwnPropertyDescriptor(obj, key); 42 | desc.writable = isWritable; 43 | props[key] = desc; 44 | } 45 | } 46 | ); 47 | 48 | Object.defineProperties(obj, props); 49 | } 50 | 51 | function unlockProperty(obj, prop, op) { 52 | var writable; 53 | 54 | if (!canWriteProtect || !obj.hasOwnProperty(prop)) { 55 | op.call(obj); 56 | return; 57 | } 58 | 59 | writable = Object.getOwnPropertyDescriptor(obj, prop).writable; 60 | Object.defineProperty(obj, prop, { writable: true }); 61 | op.call(obj); 62 | Object.defineProperty(obj, prop, { writable: writable }); 63 | } 64 | 65 | function mixin(base, mixins) { 66 | base.mixedIn = base.hasOwnProperty('mixedIn') ? base.mixedIn : []; 67 | 68 | mixins.forEach(function(mixin) { 69 | if (base.mixedIn.indexOf(mixin) == -1) { 70 | setPropertyWritability(base, false); 71 | mixin.call(base); 72 | base.mixedIn.push(mixin); 73 | } 74 | }); 75 | 76 | setPropertyWritability(base, true); 77 | } 78 | 79 | return { 80 | mixin: mixin, 81 | unlockProperty: unlockProperty 82 | }; 83 | 84 | } 85 | ); 86 | -------------------------------------------------------------------------------- /bower_components/flight/lib/debug.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [], 10 | 11 | function() { 12 | 'use strict'; 13 | 14 | // ========================================== 15 | // Search object model 16 | // ========================================== 17 | 18 | function traverse(util, searchTerm, options) { 19 | options = options || {}; 20 | var obj = options.obj || window; 21 | var path = options.path || ((obj==window) ? 'window' : ''); 22 | var props = Object.keys(obj); 23 | props.forEach(function(prop) { 24 | if ((tests[util] || util)(searchTerm, obj, prop)){ 25 | console.log([path, '.', prop].join(''), '->', ['(', typeof obj[prop], ')'].join(''), obj[prop]); 26 | } 27 | if (Object.prototype.toString.call(obj[prop]) == '[object Object]' && (obj[prop] != obj) && path.split('.').indexOf(prop) == -1) { 28 | traverse(util, searchTerm, {obj: obj[prop], path: [path,prop].join('.')}); 29 | } 30 | }); 31 | } 32 | 33 | function search(util, expected, searchTerm, options) { 34 | if (!expected || typeof searchTerm == expected) { 35 | traverse(util, searchTerm, options); 36 | } else { 37 | console.error([searchTerm, 'must be', expected].join(' ')); 38 | } 39 | } 40 | 41 | var tests = { 42 | 'name': function(searchTerm, obj, prop) {return searchTerm == prop;}, 43 | 'nameContains': function(searchTerm, obj, prop) {return prop.indexOf(searchTerm) > -1;}, 44 | 'type': function(searchTerm, obj, prop) {return obj[prop] instanceof searchTerm;}, 45 | 'value': function(searchTerm, obj, prop) {return obj[prop] === searchTerm;}, 46 | 'valueCoerced': function(searchTerm, obj, prop) {return obj[prop] == searchTerm;} 47 | }; 48 | 49 | function byName(searchTerm, options) {search('name', 'string', searchTerm, options);} 50 | function byNameContains(searchTerm, options) {search('nameContains', 'string', searchTerm, options);} 51 | function byType(searchTerm, options) {search('type', 'function', searchTerm, options);} 52 | function byValue(searchTerm, options) {search('value', null, searchTerm, options);} 53 | function byValueCoerced(searchTerm, options) {search('valueCoerced', null, searchTerm, options);} 54 | function custom(fn, options) {traverse(fn, null, options);} 55 | 56 | // ========================================== 57 | // Event logging 58 | // ========================================== 59 | 60 | var ALL = 'all'; //no filter 61 | 62 | //no logging by default 63 | var defaultEventNamesFilter = []; 64 | var defaultActionsFilter = []; 65 | 66 | var logFilter = retrieveLogFilter(); 67 | 68 | function filterEventLogsByAction(/*actions*/) { 69 | var actions = [].slice.call(arguments); 70 | 71 | logFilter.eventNames.length || (logFilter.eventNames = ALL); 72 | logFilter.actions = actions.length ? actions : ALL; 73 | saveLogFilter(); 74 | } 75 | 76 | function filterEventLogsByName(/*eventNames*/) { 77 | var eventNames = [].slice.call(arguments); 78 | 79 | logFilter.actions.length || (logFilter.actions = ALL); 80 | logFilter.eventNames = eventNames.length ? eventNames : ALL; 81 | saveLogFilter(); 82 | } 83 | 84 | function hideAllEventLogs() { 85 | logFilter.actions = []; 86 | logFilter.eventNames = []; 87 | saveLogFilter(); 88 | } 89 | 90 | function showAllEventLogs() { 91 | logFilter.actions = ALL; 92 | logFilter.eventNames = ALL; 93 | saveLogFilter(); 94 | } 95 | 96 | function saveLogFilter() { 97 | if (window.localStorage) { 98 | localStorage.setItem('logFilter_eventNames', logFilter.eventNames); 99 | localStorage.setItem('logFilter_actions', logFilter.actions); 100 | } 101 | } 102 | 103 | function retrieveLogFilter() { 104 | var result = { 105 | eventNames: (window.localStorage && localStorage.getItem('logFilter_eventNames')) || defaultEventNamesFilter, 106 | actions: (window.localStorage && localStorage.getItem('logFilter_actions')) || defaultActionsFilter 107 | }; 108 | 109 | // reconstitute arrays 110 | Object.keys(result).forEach(function(k) { 111 | var thisProp = result[k]; 112 | if (typeof thisProp == 'string' && thisProp !== ALL) { 113 | result[k] = thisProp.split(','); 114 | } 115 | }); 116 | return result; 117 | } 118 | 119 | return { 120 | 121 | enable: function(enable) { 122 | this.enabled = !!enable; 123 | 124 | if (enable && window.console) { 125 | console.info('Booting in DEBUG mode'); 126 | console.info('You can configure event logging with DEBUG.events.logAll()/logNone()/logByName()/logByAction()'); 127 | } 128 | 129 | window.DEBUG = this; 130 | }, 131 | 132 | find: { 133 | byName: byName, 134 | byNameContains: byNameContains, 135 | byType: byType, 136 | byValue: byValue, 137 | byValueCoerced: byValueCoerced, 138 | custom: custom 139 | }, 140 | 141 | events: { 142 | logFilter: logFilter, 143 | 144 | // Accepts any number of action args 145 | // e.g. DEBUG.events.logByAction("on", "off") 146 | logByAction: filterEventLogsByAction, 147 | 148 | // Accepts any number of event name args (inc. regex or wildcards) 149 | // e.g. DEBUG.events.logByName(/ui.*/, "*Thread*"); 150 | logByName: filterEventLogsByName, 151 | 152 | logAll: showAllEventLogs, 153 | logNone: hideAllEventLogs 154 | } 155 | }; 156 | } 157 | ); 158 | -------------------------------------------------------------------------------- /bower_components/flight/lib/index.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [ 10 | './advice', 11 | './component', 12 | './compose', 13 | './logger', 14 | './registry', 15 | './utils' 16 | ], 17 | 18 | function(advice, component, compose, logger, registry, utils) { 19 | 'use strict'; 20 | 21 | return { 22 | advice: advice, 23 | component: component, 24 | compose: compose, 25 | logger: logger, 26 | registry: registry, 27 | utils: utils 28 | }; 29 | 30 | } 31 | ); 32 | -------------------------------------------------------------------------------- /bower_components/flight/lib/logger.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [ 10 | './utils' 11 | ], 12 | 13 | function(utils) { 14 | 'use strict'; 15 | 16 | var actionSymbols = { 17 | on: '<-', 18 | trigger: '->', 19 | off: 'x ' 20 | }; 21 | 22 | function elemToString(elem) { 23 | var tagStr = elem.tagName ? elem.tagName.toLowerCase() : elem.toString(); 24 | var classStr = elem.className ? '.' + (elem.className) : ''; 25 | var result = tagStr + classStr; 26 | return elem.tagName ? ['\'', '\''].join(result) : result; 27 | } 28 | 29 | function log(action, component, eventArgs) { 30 | if (!window.DEBUG || !window.DEBUG.enabled) return; 31 | var name, eventType, elem, fn, logFilter, toRegExp, actionLoggable, nameLoggable; 32 | 33 | if (typeof eventArgs[eventArgs.length-1] == 'function') { 34 | fn = eventArgs.pop(); 35 | fn = fn.unbound || fn; // use unbound version if any (better info) 36 | } 37 | 38 | if (eventArgs.length == 1) { 39 | elem = component.$node[0]; 40 | eventType = eventArgs[0]; 41 | } else if (eventArgs.length == 2) { 42 | if (typeof eventArgs[1] == 'object' && !eventArgs[1].type) { 43 | elem = component.$node[0]; 44 | eventType = eventArgs[0]; 45 | } else { 46 | elem = eventArgs[0]; 47 | eventType = eventArgs[1]; 48 | } 49 | } else { 50 | elem = eventArgs[0]; 51 | eventType = eventArgs[1]; 52 | } 53 | 54 | name = typeof eventType == 'object' ? eventType.type : eventType; 55 | 56 | logFilter = DEBUG.events.logFilter; 57 | 58 | // no regex for you, actions... 59 | actionLoggable = logFilter.actions == 'all' || (logFilter.actions.indexOf(action) > -1); 60 | // event name filter allow wildcards or regex... 61 | toRegExp = function(expr) { 62 | return expr.test ? expr : new RegExp('^' + expr.replace(/\*/g, '.*') + '$'); 63 | }; 64 | nameLoggable = 65 | logFilter.eventNames == 'all' || 66 | logFilter.eventNames.some(function(e) {return toRegExp(e).test(name);}); 67 | 68 | if (actionLoggable && nameLoggable) { 69 | console.info( 70 | actionSymbols[action], 71 | action, 72 | '[' + name + ']', 73 | elemToString(elem), 74 | component.constructor.describe.split(' ').slice(0,3).join(' ') // two mixins only 75 | ); 76 | } 77 | } 78 | 79 | function withLogging() { 80 | this.before('trigger', function() { 81 | log('trigger', this, utils.toArray(arguments)); 82 | }); 83 | this.before('on', function() { 84 | log('on', this, utils.toArray(arguments)); 85 | }); 86 | this.before('off', function() { 87 | log('off', this, utils.toArray(arguments)); 88 | }); 89 | } 90 | 91 | return withLogging; 92 | } 93 | ); 94 | -------------------------------------------------------------------------------- /bower_components/flight/lib/registry.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [], 10 | 11 | function() { 12 | 'use strict'; 13 | 14 | function parseEventArgs(instance, args) { 15 | var element, type, callback; 16 | var end = args.length; 17 | 18 | if (typeof args[end - 1] === 'function') { 19 | end -= 1; 20 | callback = args[end]; 21 | } 22 | 23 | if (typeof args[end - 1] === 'object') { 24 | end -= 1; 25 | } 26 | 27 | if (end == 2) { 28 | element = args[0]; 29 | type = args[1]; 30 | } else { 31 | element = instance.node; 32 | type = args[0]; 33 | } 34 | 35 | return { 36 | element: element, 37 | type: type, 38 | callback: callback 39 | }; 40 | } 41 | 42 | function matchEvent(a, b) { 43 | return ( 44 | (a.element == b.element) && 45 | (a.type == b.type) && 46 | (b.callback == null || (a.callback == b.callback)) 47 | ); 48 | } 49 | 50 | function Registry() { 51 | 52 | var registry = this; 53 | 54 | (this.reset = function() { 55 | this.components = []; 56 | this.allInstances = {}; 57 | this.events = []; 58 | }).call(this); 59 | 60 | function ComponentInfo(component) { 61 | this.component = component; 62 | this.attachedTo = []; 63 | this.instances = {}; 64 | 65 | this.addInstance = function(instance) { 66 | var instanceInfo = new InstanceInfo(instance); 67 | this.instances[instance.identity] = instanceInfo; 68 | this.attachedTo.push(instance.node); 69 | 70 | return instanceInfo; 71 | }; 72 | 73 | this.removeInstance = function(instance) { 74 | delete this.instances[instance.identity]; 75 | var indexOfNode = this.attachedTo.indexOf(instance.node); 76 | (indexOfNode > -1) && this.attachedTo.splice(indexOfNode, 1); 77 | 78 | if (!Object.keys(this.instances).length) { 79 | //if I hold no more instances remove me from registry 80 | registry.removeComponentInfo(this); 81 | } 82 | }; 83 | 84 | this.isAttachedTo = function(node) { 85 | return this.attachedTo.indexOf(node) > -1; 86 | }; 87 | } 88 | 89 | function InstanceInfo(instance) { 90 | this.instance = instance; 91 | this.events = []; 92 | 93 | this.addBind = function(event) { 94 | this.events.push(event); 95 | registry.events.push(event); 96 | }; 97 | 98 | this.removeBind = function(event) { 99 | for (var i = 0, e; e = this.events[i]; i++) { 100 | if (matchEvent(e, event)) { 101 | this.events.splice(i, 1); 102 | } 103 | } 104 | }; 105 | } 106 | 107 | this.addInstance = function(instance) { 108 | var component = this.findComponentInfo(instance); 109 | 110 | if (!component) { 111 | component = new ComponentInfo(instance.constructor); 112 | this.components.push(component); 113 | } 114 | 115 | var inst = component.addInstance(instance); 116 | 117 | this.allInstances[instance.identity] = inst; 118 | 119 | return component; 120 | }; 121 | 122 | this.removeInstance = function(instance) { 123 | var index, instInfo = this.findInstanceInfo(instance); 124 | 125 | //remove from component info 126 | var componentInfo = this.findComponentInfo(instance); 127 | componentInfo && componentInfo.removeInstance(instance); 128 | 129 | //remove from registry 130 | delete this.allInstances[instance.identity]; 131 | }; 132 | 133 | this.removeComponentInfo = function(componentInfo) { 134 | var index = this.components.indexOf(componentInfo); 135 | (index > -1) && this.components.splice(index, 1); 136 | }; 137 | 138 | this.findComponentInfo = function(which) { 139 | var component = which.attachTo ? which : which.constructor; 140 | 141 | for (var i = 0, c; c = this.components[i]; i++) { 142 | if (c.component === component) { 143 | return c; 144 | } 145 | } 146 | 147 | return null; 148 | }; 149 | 150 | this.findInstanceInfo = function(instance) { 151 | return this.allInstances[instance.identity] || null; 152 | }; 153 | 154 | this.findInstanceInfoByNode = function(node) { 155 | var result = []; 156 | Object.keys(this.allInstances).forEach(function(k) { 157 | var thisInstanceInfo = this.allInstances[k]; 158 | if (thisInstanceInfo.instance.node === node) { 159 | result.push(thisInstanceInfo); 160 | } 161 | }, this); 162 | return result; 163 | }; 164 | 165 | this.on = function(componentOn) { 166 | var instance = registry.findInstanceInfo(this), boundCallback; 167 | 168 | // unpacking arguments by hand benchmarked faster 169 | var l = arguments.length, i = 1; 170 | var otherArgs = new Array(l - 1); 171 | for (; i < l; i++) otherArgs[i - 1] = arguments[i]; 172 | 173 | if (instance) { 174 | boundCallback = componentOn.apply(null, otherArgs); 175 | if (boundCallback) { 176 | otherArgs[otherArgs.length-1] = boundCallback; 177 | } 178 | var event = parseEventArgs(this, otherArgs); 179 | instance.addBind(event); 180 | } 181 | }; 182 | 183 | this.off = function(/*el, type, callback*/) { 184 | var event = parseEventArgs(this, arguments), 185 | instance = registry.findInstanceInfo(this); 186 | 187 | if (instance) { 188 | instance.removeBind(event); 189 | } 190 | 191 | //remove from global event registry 192 | for (var i = 0, e; e = registry.events[i]; i++) { 193 | if (matchEvent(e, event)) { 194 | registry.events.splice(i, 1); 195 | } 196 | } 197 | }; 198 | 199 | // debug tools may want to add advice to trigger 200 | registry.trigger = function() {}; 201 | 202 | this.teardown = function() { 203 | registry.removeInstance(this); 204 | }; 205 | 206 | this.withRegistration = function() { 207 | this.after('initialize', function() { 208 | registry.addInstance(this); 209 | }); 210 | 211 | this.around('on', registry.on); 212 | this.after('off', registry.off); 213 | //debug tools may want to add advice to trigger 214 | window.DEBUG && DEBUG.enabled && this.after('trigger', registry.trigger); 215 | this.after('teardown', {obj: registry, fnName: 'teardown'}); 216 | }; 217 | 218 | } 219 | 220 | return new Registry; 221 | } 222 | ); 223 | -------------------------------------------------------------------------------- /bower_components/flight/lib/utils.js: -------------------------------------------------------------------------------- 1 | // ========================================== 2 | // Copyright 2013 Twitter, Inc 3 | // Licensed under The MIT License 4 | // http://opensource.org/licenses/MIT 5 | // ========================================== 6 | 7 | define( 8 | 9 | [], 10 | 11 | function() { 12 | 'use strict'; 13 | 14 | var arry = []; 15 | var DEFAULT_INTERVAL = 100; 16 | 17 | var utils = { 18 | 19 | isDomObj: function(obj) { 20 | return !!(obj.nodeType || (obj === window)); 21 | }, 22 | 23 | toArray: function(obj, from) { 24 | return arry.slice.call(obj, from); 25 | }, 26 | 27 | // returns new object representing multiple objects merged together 28 | // optional final argument is boolean which specifies if merge is recursive 29 | // original objects are unmodified 30 | // 31 | // usage: 32 | // var base = {a:2, b:6}; 33 | // var extra = {b:3, c:4}; 34 | // merge(base, extra); //{a:2, b:3, c:4} 35 | // base; //{a:2, b:6} 36 | // 37 | // var base = {a:2, b:6}; 38 | // var extra = {b:3, c:4}; 39 | // var extraExtra = {a:4, d:9}; 40 | // merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9} 41 | // base; //{a:2, b:6} 42 | // 43 | // var base = {a:2, b:{bb:4, cc:5}}; 44 | // var extra = {a:4, b:{cc:7, dd:1}}; 45 | // merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}} 46 | // base; //{a:2, b:6} 47 | 48 | merge: function(/*obj1, obj2,....deepCopy*/) { 49 | // unpacking arguments by hand benchmarked faster 50 | var l = arguments.length, 51 | i = 0, 52 | args = new Array(l + 1); 53 | for (; i < l; i++) args[i + 1] = arguments[i]; 54 | 55 | if (l === 0) { 56 | return {}; 57 | } 58 | 59 | //start with empty object so a copy is created 60 | args[0] = {}; 61 | 62 | if (args[args.length - 1] === true) { 63 | //jquery extend requires deep copy as first arg 64 | args.pop(); 65 | args.unshift(true); 66 | } 67 | 68 | return $.extend.apply(undefined, args); 69 | }, 70 | 71 | // updates base in place by copying properties of extra to it 72 | // optionally clobber protected 73 | // usage: 74 | // var base = {a:2, b:6}; 75 | // var extra = {c:4}; 76 | // push(base, extra); //{a:2, b:6, c:4} 77 | // base; //{a:2, b:6, c:4} 78 | // 79 | // var base = {a:2, b:6}; 80 | // var extra = {b: 4 c:4}; 81 | // push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode") 82 | // base; //{a:2, b:6} 83 | // 84 | // objects with the same key will merge recursively when protect is false 85 | // eg: 86 | // var base = {a:16, b:{bb:4, cc:10}}; 87 | // var extra = {b:{cc:25, dd:19}, c:5}; 88 | // push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5} 89 | // 90 | push: function(base, extra, protect) { 91 | if (base) { 92 | Object.keys(extra || {}).forEach(function(key) { 93 | if (base[key] && protect) { 94 | throw new Error('utils.push attempted to overwrite "' + key + '" while running in protected mode'); 95 | } 96 | 97 | if (typeof base[key] == 'object' && typeof extra[key] == 'object') { 98 | // recurse 99 | this.push(base[key], extra[key]); 100 | } else { 101 | // no protect, so extra wins 102 | base[key] = extra[key]; 103 | } 104 | }, this); 105 | } 106 | 107 | return base; 108 | }, 109 | 110 | isEnumerable: function(obj, property) { 111 | return Object.keys(obj).indexOf(property) > -1; 112 | }, 113 | 114 | // build a function from other function(s) 115 | // utils.compose(a,b,c) -> a(b(c())); 116 | // implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas 117 | compose: function() { 118 | var funcs = arguments; 119 | 120 | return function() { 121 | var args = arguments; 122 | 123 | for (var i = funcs.length-1; i >= 0; i--) { 124 | args = [funcs[i].apply(this, args)]; 125 | } 126 | 127 | return args[0]; 128 | }; 129 | }, 130 | 131 | // Can only unique arrays of homogeneous primitives, e.g. an array of only strings, an array of only booleans, or an array of only numerics 132 | uniqueArray: function(array) { 133 | var u = {}, a = []; 134 | 135 | for (var i = 0, l = array.length; i < l; ++i) { 136 | if (u.hasOwnProperty(array[i])) { 137 | continue; 138 | } 139 | 140 | a.push(array[i]); 141 | u[array[i]] = 1; 142 | } 143 | 144 | return a; 145 | }, 146 | 147 | debounce: function(func, wait, immediate) { 148 | if (typeof wait != 'number') { 149 | wait = DEFAULT_INTERVAL; 150 | } 151 | 152 | var timeout, result; 153 | 154 | return function() { 155 | var context = this, args = arguments; 156 | var later = function() { 157 | timeout = null; 158 | if (!immediate) { 159 | result = func.apply(context, args); 160 | } 161 | }; 162 | var callNow = immediate && !timeout; 163 | 164 | clearTimeout(timeout); 165 | timeout = setTimeout(later, wait); 166 | 167 | if (callNow) { 168 | result = func.apply(context, args); 169 | } 170 | 171 | return result; 172 | }; 173 | }, 174 | 175 | throttle: function(func, wait) { 176 | if (typeof wait != 'number') { 177 | wait = DEFAULT_INTERVAL; 178 | } 179 | 180 | var context, args, timeout, throttling, more, result; 181 | var whenDone = this.debounce(function(){ 182 | more = throttling = false; 183 | }, wait); 184 | 185 | return function() { 186 | context = this; args = arguments; 187 | var later = function() { 188 | timeout = null; 189 | if (more) { 190 | result = func.apply(context, args); 191 | } 192 | whenDone(); 193 | }; 194 | 195 | if (!timeout) { 196 | timeout = setTimeout(later, wait); 197 | } 198 | 199 | if (throttling) { 200 | more = true; 201 | } else { 202 | throttling = true; 203 | result = func.apply(context, args); 204 | } 205 | 206 | whenDone(); 207 | return result; 208 | }; 209 | }, 210 | 211 | countThen: function(num, base) { 212 | return function() { 213 | if (!--num) { return base.apply(this, arguments); } 214 | }; 215 | }, 216 | 217 | delegate: function(rules) { 218 | return function(e, data) { 219 | var target = $(e.target), parent; 220 | 221 | Object.keys(rules).forEach(function(selector) { 222 | if (!e.isPropagationStopped() && (parent = target.closest(selector)).length) { 223 | data = data || {}; 224 | data.el = parent[0]; 225 | return rules[selector].apply(this, [e, data]); 226 | } 227 | }, this); 228 | }; 229 | }, 230 | 231 | // ensures that a function will only be called once. 232 | // usage: 233 | // will only create the application once 234 | // var initialize = utils.once(createApplication) 235 | // initialize(); 236 | // initialize(); 237 | // 238 | // will only delete a record once 239 | // var myHanlder = function () { 240 | // $.ajax({type: 'DELETE', url: 'someurl.com', data: {id: 1}}); 241 | // }; 242 | // this.on('click', utils.once(myHandler)); 243 | // 244 | once: function(func) { 245 | var ran, result; 246 | 247 | return function() { 248 | if (ran) { 249 | return result; 250 | } 251 | 252 | result = func.apply(this, arguments); 253 | ran = true; 254 | 255 | return result; 256 | }; 257 | } 258 | 259 | }; 260 | 261 | return utils; 262 | } 263 | ); 264 | -------------------------------------------------------------------------------- /bower_components/jasmine-flight/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-flight", 3 | "version": "2.2.0", 4 | "description": "Extensions to the Jasmine test framework for use with Flight", 5 | "main": "lib/jasmine-flight.js", 6 | "dependencies": { 7 | "jasmine-jquery": "~1.7.0" 8 | }, 9 | "devDependencies": { 10 | "flight": "~1.1.0" 11 | }, 12 | "keywords": [ 13 | "flight", 14 | "jasmine", 15 | "test" 16 | ], 17 | "ignore": [ 18 | ".*", 19 | "test", 20 | "package.json", 21 | "karma.conf.js", 22 | "CONTRIBUTING.md", 23 | "CHANGELOG.md" 24 | ], 25 | "homepage": "https://github.com/flightjs/jasmine-flight", 26 | "_release": "2.2.0", 27 | "_resolution": { 28 | "type": "version", 29 | "tag": "2.2.0", 30 | "commit": "3af47a6febe2d27b71de234d9dcf47e1f7b2b7e5" 31 | }, 32 | "_source": "git://github.com/flightjs/jasmine-flight.git", 33 | "_target": "~2.2.0", 34 | "_originalSource": "jasmine-flight", 35 | "_direct": true 36 | } -------------------------------------------------------------------------------- /bower_components/jasmine-flight/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Twitter, Inc and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /bower_components/jasmine-flight/README.md: -------------------------------------------------------------------------------- 1 | # jasmine-flight [![Build Status](https://travis-ci.org/flightjs/jasmine-flight.png?branch=master)](http://travis-ci.org/flightjs/jasmine-flight) 2 | 3 | Extensions to the Jasmine test framework for use with [Flight](https://github.com/flightjs/flight) 4 | 5 | # Getting started 6 | 7 | Include [jasmine-flight.js](https://raw.github.com/flightjs/jasmine-flight/master/lib/jasmine-flight.js) 8 | in your app and load it in your test runner. 9 | 10 | Or install it with [Bower](http://bower.io/): 11 | 12 | ```bash 13 | bower install --save-dev jasmine-flight 14 | ``` 15 | 16 | **N.B.** jasmine-flight depends on 17 | [jasmine](https://github.com/pivotal/jasmine) and 18 | [jasmine-jquery](https://github.com/velesin/jasmine-jquery) 19 | 20 | jasmine-flight assumes you'll be using RequireJS to load Flight modules, and 21 | that you've configured the Flight directory path. For example: 22 | 23 | ```javascript 24 | requirejs.config({ 25 | paths: { 26 | flight: 'bower_components/flight' 27 | } 28 | }); 29 | ``` 30 | 31 | ## Components 32 | 33 | ```javascript 34 | describeComponent('path/to/component', function () { 35 | beforeEach(function () { 36 | setupComponent(); 37 | }); 38 | 39 | it('should do x', function () { 40 | // a component instance is now accessible as this.component 41 | // the component root node is attached to the DOM 42 | // the component root node is also available as this.$node 43 | }); 44 | }); 45 | ``` 46 | 47 | ## Mixins 48 | 49 | ```javascript 50 | describeMixin('path/to/mixin', function () { 51 | // initialize the component and attach it to the DOM 52 | beforeEach(function () { 53 | setupComponent(); 54 | }); 55 | 56 | it('should do x', function () { 57 | expect(this.component.doSomething()).toBe(expected); 58 | }); 59 | }); 60 | ``` 61 | 62 | ## Event spy 63 | 64 | ```javascript 65 | describeComponent('data/twitter_profile', function () { 66 | beforeEach(function () { 67 | setupComponent(); 68 | }); 69 | 70 | describe('listens for uiNeedsTwitterUserId', function () { 71 | // was the event triggered? 72 | it('and triggers dataTwitterUserId', function () { 73 | var eventSpy = spyOnEvent(document, 'dataTwitterProfile'); 74 | $(document).trigger('uiNeedsTwitterUserId', { 75 | screen_name: 'tbrd' 76 | }); 77 | expect(eventSpy).toHaveBeenTriggeredOn(document); 78 | }); 79 | 80 | // is the user id correct? 81 | it('and has correct id', function () { 82 | var eventSpy = spyOnEvent(document, 'dataTwitterUserId'); 83 | $(document).trigger('uiNeedsTwitteruserId', { 84 | screen_name: 'tbrd' 85 | }); 86 | expect(eventSpy.mostRecentCall.data).toEqual({ 87 | screen_name: 'tbrd', 88 | id: 4149861 89 | }); 90 | }); 91 | }); 92 | }); 93 | ``` 94 | 95 | ## setupComponent 96 | 97 | ```javascript 98 | setupComponent(optionalFixture, optionalOptions); 99 | ``` 100 | 101 | Calling `setupComponent` twice will create an instance, tear it down and create a new one. 102 | 103 | ### HTML Fixtures 104 | 105 | ```javascript 106 | describeComponent('ui/twitter_profile', function () { 107 | // is the component attached to the fixture? 108 | it('this.component.$node has class "foo"', function () { 109 | setupComponent('Test'); 110 | expect(this.component.$node).toHaveClass('foo'); 111 | }); 112 | }); 113 | ``` 114 | 115 | ### Component Options 116 | 117 | ```javascript 118 | describeComponent('data/twitter_profile', function () { 119 | // is the option set correctly? 120 | it('this.component.attr.baseUrl is set', function () { 121 | setupComponent({ 122 | baseUrl: 'http://twitter.com/1.1/' 123 | }); 124 | expect(this.component.attr.baseUrl).toBe('http://twitter.com/1.1/'); 125 | }); 126 | }); 127 | ``` 128 | 129 | # Teardown 130 | 131 | Components are automatically torn down after each test. 132 | 133 | ## Contributing to this project 134 | 135 | Anyone and everyone is welcome to contribute. Please take a moment to 136 | review the [guidelines for contributing](CONTRIBUTING.md). 137 | 138 | * [Bug reports](CONTRIBUTING.md#bugs) 139 | * [Feature requests](CONTRIBUTING.md#features) 140 | * [Pull requests](CONTRIBUTING.md#pull-requests) 141 | 142 | ## Authors 143 | 144 | * [@tbrd](http://github.com/tbrd) 145 | 146 | ## Thanks 147 | 148 | * [@esbie](http://github.com/esbie) and 149 | [@skilldrick](http://github.com/skilldrick) for creating the original 150 | `describeComponent` & `describeMixin` methods. 151 | * [@necolas](http://github.com/necolas) for ongoing support & development 152 | 153 | ## License 154 | 155 | Copyright 2013 Twitter, Inc and other contributors. 156 | 157 | Licensed under the MIT License 158 | -------------------------------------------------------------------------------- /bower_components/jasmine-flight/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-flight", 3 | "version": "2.2.0", 4 | "description": "Extensions to the Jasmine test framework for use with Flight", 5 | "main": "lib/jasmine-flight.js", 6 | "dependencies": { 7 | "jasmine-jquery": "~1.7.0" 8 | }, 9 | "devDependencies": { 10 | "flight": "~1.1.0" 11 | }, 12 | "keywords": [ 13 | "flight", 14 | "jasmine", 15 | "test" 16 | ], 17 | "ignore": [ 18 | ".*", 19 | "test", 20 | "package.json", 21 | "karma.conf.js", 22 | "CONTRIBUTING.md", 23 | "CHANGELOG.md" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /bower_components/jasmine-flight/lib/jasmine-flight.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013, Twitter Inc. and other contributors 3 | * Licensed under the MIT License 4 | */ 5 | 6 | (function (root) { 7 | 'use strict'; 8 | 9 | jasmine.flight = {}; 10 | 11 | /** 12 | * Wrapper for describe. Load component before each test. 13 | * 14 | * @param componentPath 15 | * @param specDefinitions 16 | */ 17 | 18 | root.describeComponent = function (componentPath, specDefinitions) { 19 | jasmine.getEnv().describeComponent(componentPath, specDefinitions); 20 | }; 21 | 22 | root.ddescribeComponent = function (componentPath, specDefinitions) { 23 | jasmine.getEnv().ddescribeComponent(componentPath, specDefinitions); 24 | }; 25 | 26 | var describeComponentFactory = function (componentPath, specDefinitions) { 27 | return function () { 28 | beforeEach(function () { 29 | this.Component = this.component = this.$node = null; 30 | 31 | var requireCallback = function (registry, Component) { 32 | registry.reset(); 33 | this.Component = Component; 34 | }.bind(this); 35 | 36 | require(['flight/lib/registry', componentPath], requireCallback); 37 | 38 | waitsFor(function () { 39 | return this.Component !== null; 40 | }.bind(this)); 41 | }); 42 | 43 | afterEach(function () { 44 | if (this.$node) { 45 | this.$node.remove(); 46 | this.$node = null; 47 | } 48 | 49 | var requireCallback = function (defineComponent) { 50 | if (this.component) { 51 | this.component = null; 52 | } 53 | 54 | this.Component = null; 55 | defineComponent.teardownAll(); 56 | }.bind(this); 57 | 58 | require(['flight/lib/component'], requireCallback); 59 | 60 | waitsFor(function () { 61 | return this.Component === null; 62 | }.bind(this)); 63 | }); 64 | 65 | specDefinitions.apply(this); 66 | }; 67 | }; 68 | 69 | jasmine.Env.prototype.describeComponent = function (componentPath, specDefinitions) { 70 | describe(componentPath, describeComponentFactory(componentPath, specDefinitions)); 71 | }; 72 | 73 | jasmine.Env.prototype.ddescribeComponent = function (componentPath, specDefinitions) { 74 | ddescribe(componentPath, describeComponentFactory(componentPath, specDefinitions)); 75 | }; 76 | 77 | /** 78 | * Wrapper for describe. Load mixin before each test. 79 | * 80 | * @param mixinPath 81 | * @param specDefinitions 82 | */ 83 | 84 | root.describeMixin = function (mixinPath, specDefinitions) { 85 | jasmine.getEnv().describeMixin(mixinPath, specDefinitions); 86 | }; 87 | 88 | root.ddescribeMixin = function (mixinPath, specDefinitions) { 89 | jasmine.getEnv().ddescribeMixin(mixinPath, specDefinitions); 90 | }; 91 | 92 | var describeMixinFactory = function (mixinPath, specDefinitions) { 93 | return function () { 94 | beforeEach(function () { 95 | this.Component = this.component = this.$node = null; 96 | 97 | var requireCallback = function (registry, defineComponent, Mixin) { 98 | registry.reset(); 99 | this.Component = defineComponent(function () {}, Mixin); 100 | }.bind(this); 101 | 102 | require(['flight/lib/registry', 'flight/lib/component', mixinPath], requireCallback); 103 | 104 | waitsFor(function () { 105 | return this.Component !== null; 106 | }); 107 | }); 108 | 109 | afterEach(function () { 110 | if (this.$node) { 111 | this.$node.remove(); 112 | this.$node = null; 113 | } 114 | 115 | var requireCallback = function (defineComponent) { 116 | if (this.component) { 117 | this.component = null; 118 | } 119 | 120 | this.Component = null; 121 | defineComponent.teardownAll(); 122 | }.bind(this); 123 | 124 | require(['flight/lib/component'], requireCallback); 125 | 126 | waitsFor(function () { 127 | return this.Component === null; 128 | }.bind(this)); 129 | }); 130 | 131 | specDefinitions.apply(this); 132 | }; 133 | }; 134 | 135 | jasmine.Env.prototype.describeMixin = function (mixinPath, specDefinitions) { 136 | describe(mixinPath, describeMixinFactory(mixinPath, specDefinitions)); 137 | }; 138 | 139 | jasmine.Env.prototype.ddescribeMixin = function (mixinPath, specDefinitions) { 140 | ddescribe(mixinPath, describeMixinFactory(mixinPath, specDefinitions)); 141 | }; 142 | 143 | /** 144 | * Wrapper for describe. Load module before each test. 145 | * 146 | * @param modulePath 147 | * @param specDefinitions 148 | */ 149 | 150 | root.describeModule = function (modulePath, specDefinitions) { 151 | return jasmine.getEnv().describeModule(modulePath, specDefinitions); 152 | }; 153 | 154 | jasmine.Env.prototype.describeModule = function (modulePath, specDefinitions) { 155 | describe(modulePath, function () { 156 | beforeEach(function () { 157 | this.module = null; 158 | 159 | var requireCallback = function (module) { 160 | this.module = module; 161 | }.bind(this); 162 | 163 | require([modulePath], requireCallback); 164 | 165 | waitsFor(function () { 166 | return this.module !== null; 167 | }); 168 | }); 169 | 170 | specDefinitions.apply(this); 171 | }); 172 | }; 173 | 174 | /** 175 | * Create root node and initialize component. Fixture should be html string 176 | * or jQuery object. 177 | * 178 | * @param fixture {String} (Optional) 179 | * @param options {Options} (Optional) 180 | */ 181 | 182 | root.setupComponent = function (fixture, options) { 183 | jasmine.getEnv().currentSpec.setupComponent(fixture, options); 184 | }; 185 | 186 | jasmine.Spec.prototype.setupComponent = function (fixture, options) { 187 | if (this.component) { 188 | this.component.teardown(); 189 | this.$node.remove(); 190 | } 191 | 192 | if (fixture instanceof jQuery || typeof fixture === 'string') { 193 | this.$node = $(fixture).addClass('component-root'); 194 | } else { 195 | this.$node = $('
'); 196 | options = fixture; 197 | fixture = null; 198 | } 199 | 200 | $('body').append(this.$node); 201 | 202 | options = options === undefined ? {} : options; 203 | 204 | this.component = (new this.Component()).initialize(this.$node, options); 205 | }; 206 | 207 | 208 | (function (namespace) { 209 | var eventsData = { 210 | spiedEvents: {}, 211 | handlers: [] 212 | }; 213 | 214 | namespace.formatElement = function ($element) { 215 | var limit = 200; 216 | var output = ''; 217 | 218 | if ($element instanceof jQuery) { 219 | output = jasmine.jQuery.elementToString($element); 220 | if (output.length > limit) { 221 | output = output.slice(0, 200) + '...'; 222 | } 223 | } else { 224 | //$element should always be a jQuery object 225 | output = 'element is not a jQuery object'; 226 | } 227 | 228 | return output; 229 | }; 230 | 231 | namespace.compareColors = function (color1, color2) { 232 | if (color1.charAt(0) === color2.charAt(0)) { 233 | return color1 === color2; 234 | } else { 235 | return namespace.hex2rgb(color1) === namespace.hex2rgb(color2); 236 | } 237 | }; 238 | 239 | namespace.hex2rgb = function (colorString) { 240 | if (colorString.charAt(0) !== '#') return colorString; 241 | // note: hexStr should be #rrggbb 242 | var hex = parseInt(colorString.substring(1), 16); 243 | var r = (hex & 0xff0000) >> 16; 244 | var g = (hex & 0x00ff00) >> 8; 245 | var b = hex & 0x0000ff; 246 | return 'rgb(' + r + ', ' + g + ', ' + b + ')'; 247 | }; 248 | 249 | namespace.events = { 250 | spyOn: function (selector, eventName) { 251 | eventsData.spiedEvents[[selector, eventName]] = { 252 | callCount: 0, 253 | calls: [], 254 | mostRecentCall: {}, 255 | name: eventName 256 | }; 257 | 258 | var handler = function (e, data) { 259 | var call = { 260 | event: e, 261 | args: jasmine.util.argsToArray(arguments), 262 | data: data 263 | }; 264 | eventsData.spiedEvents[[selector, eventName]].callCount++; 265 | eventsData.spiedEvents[[selector, eventName]].calls.push(call); 266 | eventsData.spiedEvents[[selector, eventName]].mostRecentCall = call; 267 | }; 268 | 269 | jQuery(selector).on(eventName, handler); 270 | eventsData.handlers.push([selector, eventName, handler]); 271 | return eventsData.spiedEvents[[selector, eventName]]; 272 | }, 273 | 274 | eventArgs: function (selector, eventName, expectedArg) { 275 | var actualArgs = eventsData.spiedEvents[[selector, eventName]].mostRecentCall.args; 276 | 277 | if (!actualArgs) { 278 | throw 'No event spy found on ' + eventName + '. Try adding a call to spyOnEvent or make sure that the selector the event is triggered on and the selector being spied on are correct.'; 279 | } 280 | 281 | // remove extra event metadata if it is not tested for 282 | if ((actualArgs.length === 2) && typeof actualArgs[1] === 'object' && 283 | expectedArg && !expectedArg.scribeContext && !expectedArg.sourceEventData && !expectedArg.scribeData) { 284 | actualArgs[1] = $.extend({}, actualArgs[1]); 285 | delete actualArgs[1].sourceEventData; 286 | delete actualArgs[1].scribeContext; 287 | delete actualArgs[1].scribeData; 288 | } 289 | 290 | return actualArgs; 291 | }, 292 | 293 | wasTriggered: function (selector, event) { 294 | var spiedEvent = eventsData.spiedEvents[[selector, event]]; 295 | return spiedEvent && spiedEvent.callCount > 0; 296 | }, 297 | 298 | wasTriggeredWith: function (selector, eventName, expectedArg, env) { 299 | var actualArgs = jasmine.flight.events.eventArgs(selector, eventName, expectedArg); 300 | return actualArgs && env.contains_(actualArgs, expectedArg); 301 | }, 302 | 303 | wasTriggeredWithData: function (selector, eventName, expectedArg, env) { 304 | var actualArgs = jasmine.flight.events.eventArgs(selector, eventName, expectedArg); 305 | var valid; 306 | 307 | if (actualArgs) { 308 | valid = false; 309 | for (var i = 0; i < actualArgs.length; i++) { 310 | if (jasmine.flight.validateHash(expectedArg, actualArgs[i])) { 311 | return true; 312 | } 313 | } 314 | return valid; 315 | } 316 | 317 | return false; 318 | }, 319 | 320 | cleanUp: function () { 321 | eventsData.spiedEvents = {}; 322 | // unbind all handlers 323 | for (var i = 0; i < eventsData.handlers.length; i++) { 324 | jQuery(eventsData.handlers[i][0]).off(eventsData.handlers[i][1], eventsData.handlers[i][2]); 325 | } 326 | eventsData.handlers = []; 327 | } 328 | }; 329 | 330 | namespace.validateHash = function (a, b, intersection) { 331 | var validHash; 332 | for (var field in a) { 333 | if ((typeof a[field] === 'object') && (typeof b[field] === 'object')) { 334 | validHash = a[field] === b[field] || jasmine.flight.validateHash(a[field], b[field]); 335 | } else if (intersection && (typeof a[field] === 'undefined' || typeof b[field] === 'undefined')) { 336 | validHash = true; 337 | } else { 338 | validHash = (a[field] === b[field]); 339 | } 340 | if (!validHash) { 341 | break; 342 | } 343 | } 344 | return validHash; 345 | }; 346 | 347 | namespace.assertEventTriggeredWithData = function (selector, expectedArg, fuzzyMatch) { 348 | var eventName = typeof this.actual === 'string' ? this.actual : this.actual.name; 349 | var wasTriggered = jasmine.flight.events.wasTriggered(selector, eventName); 350 | 351 | this.message = function () { 352 | var $pp = function (obj) { 353 | var description; 354 | var attr; 355 | 356 | if (!(obj instanceof jQuery)) { 357 | obj = $(obj); 358 | } 359 | 360 | description = [ 361 | obj.get(0).nodeName 362 | ]; 363 | 364 | attr = obj.get(0).attributes || []; 365 | 366 | for (var x = 0; x < attr.length; x++) { 367 | description.push(attr[x].name + '="' + attr[x].value + '"'); 368 | } 369 | 370 | return '<' + description.join(' ') + '>'; 371 | }; 372 | 373 | if (wasTriggered) { 374 | var actualArg = jasmine.flight.events.eventArgs(selector, eventName, expectedArg)[1]; 375 | return [ 376 | '
Expected event ' + eventName + ' to have been triggered on' + selector, 377 | '
Expected event ' + eventName + ' not to have been triggered on' + selector 378 | ]; 379 | } else { 380 | return [ 381 | 'Expected event ' + eventName + ' to have been triggered on ' + $pp(selector), 382 | 'Expected event ' + eventName + ' not to have been triggered on ' + $pp(selector) 383 | ]; 384 | } 385 | }; 386 | 387 | if (!wasTriggered) { 388 | return false; 389 | } 390 | 391 | if (fuzzyMatch) { 392 | return jasmine.flight.events.wasTriggeredWithData(selector, eventName, expectedArg, this.env); 393 | } else { 394 | return jasmine.flight.events.wasTriggeredWith(selector, eventName, expectedArg, this.env); 395 | } 396 | 397 | }; 398 | })(jasmine.flight); 399 | 400 | beforeEach(function () { 401 | this.addMatchers({ 402 | toHaveBeenTriggeredOn: function () { 403 | var selector = arguments[0]; 404 | var eventName = typeof this.actual === 'string' ? this.actual : this.actual.name; 405 | var wasTriggered = jasmine.flight.events.wasTriggered(selector, eventName); 406 | 407 | this.message = function () { 408 | var $pp = function (obj) { 409 | var description; 410 | var attr; 411 | 412 | if (!(obj instanceof jQuery)) { 413 | obj = $(obj); 414 | } 415 | 416 | description = [ 417 | obj.get(0).nodeName 418 | ]; 419 | 420 | attr = obj.get(0).attributes || []; 421 | 422 | for (var x = 0; x < attr.length; x++) { 423 | description.push(attr[x].name + '="' + attr[x].value + '"'); 424 | } 425 | 426 | return '<' + description.join(' ') + '>'; 427 | }; 428 | 429 | if (wasTriggered) { 430 | return [ 431 | '
Expected event ' + eventName + ' to have been triggered on' + selector, 432 | '
Expected event ' + eventName + ' not to have been triggered on' + selector 433 | ]; 434 | } else { 435 | return [ 436 | 'Expected event ' + eventName + ' to have been triggered on ' + $pp(selector), 437 | 'Expected event ' + eventName + ' not to have been triggered on ' + $pp(selector) 438 | ]; 439 | } 440 | }; 441 | 442 | return wasTriggered; 443 | }, 444 | 445 | toHaveBeenTriggeredOnAndWith: function (selector, expectedArg, fuzzyMatch) { 446 | return jasmine.flight.assertEventTriggeredWithData.call(this, selector, expectedArg, fuzzyMatch); 447 | }, 448 | 449 | toHaveBeenTriggeredOnAndWithFuzzy: function (selector, expectedArg) { 450 | return jasmine.flight.assertEventTriggeredWithData.call(this, selector, expectedArg, true); 451 | }, 452 | 453 | toHaveCss: function (prop, val) { 454 | var result; 455 | if (val instanceof RegExp) { 456 | result = val.test(this.actual.css(prop)); 457 | } else if (prop.match(/color/)) { 458 | //IE returns colors as hex strings; other browsers return rgb(r, g, b) strings 459 | result = jasmine.flight.compareColors(this.actual.css(prop), val); 460 | } else { 461 | result = this.actual.css(prop) === val; 462 | //sometimes .css() returns strings when it should return numbers 463 | if (!result && typeof val === 'number') { 464 | result = parseFloat(this.actual.css(prop), 10) === val; 465 | } 466 | } 467 | 468 | this.actual = jasmine.flight.formatElement(this.actual); 469 | return result; 470 | } 471 | }); 472 | }); 473 | 474 | root.spyOnEvent = function (selector, eventName) { 475 | jasmine.jQuery.events.spyOn(selector, eventName); 476 | return jasmine.flight.events.spyOn(selector, eventName); 477 | }; 478 | 479 | afterEach(function () { 480 | jasmine.flight.events.cleanUp(); 481 | }); 482 | 483 | }(this)); 484 | -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-jquery", 3 | "version": "1.7.0", 4 | "main": "lib/jasmine-jquery.js", 5 | "ignore": [ 6 | "test", 7 | "vendor", 8 | ".*", 9 | "HISTORY.md", 10 | "SpecRunner.html" 11 | ], 12 | "homepage": "https://github.com/velesin/jasmine-jquery", 13 | "_release": "1.7.0", 14 | "_resolution": { 15 | "type": "version", 16 | "tag": "1.7.0", 17 | "commit": "95120d9aac9074d1b6f9c2c043f67e64e99e3e35" 18 | }, 19 | "_source": "git://github.com/velesin/jasmine-jquery.git", 20 | "_target": "~1.7.0", 21 | "_originalSource": "jasmine-jquery" 22 | } -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Jasmine-jQuery 2 | 3 | Looking to contribute something to Jasmine-jQuery? **Here's how you can help.** 4 | 5 | ## Reporting issues 6 | 7 | We only accept issues that are bug reports or feature requests. Bugs must be isolated and reproducible problems that we can fix within the Jasmine-jQuery. Please read the following guidelines before opening any issue. 8 | 9 | 1. **Search for existing issues.** Somemeone else may have reported the same issue. Moreover, the issue may have already been resolved with a fix available. 10 | 2. **Create an isolated and reproducible test case.** Be sure the problem exists in Jasmine-jQuery's code with a [reduced test case](http://css-tricks.com/reduced-test-cases/) that should be included in each bug report. 11 | 3. **Include a live example.** Make use of jsFiddle or jsBin to share your isolated test cases. 12 | 4. **Share as much information as possible.** Include operating system and version, browser and version, version of Jasmine-jQuery. Also include steps to reproduce the bug. 13 | 14 | ## Pull requests 15 | 16 | - Try not to pollute your pull request with unintended changes--keep them simple and small 17 | - Please squash your commits when appropriate. This simplifies future cherry picks, and also keeps the git log clean. 18 | - Include tests that fail without your code, and pass with it. 19 | - Update the documentation, examples elsewhere, and the guides: whatever is affected by your contribution. 20 | - If you can, have another developer sanity check your change. 21 | 22 | ## Coding standards 23 | 24 | - No semicolons 25 | - Comma first 26 | - 2 spaces (no tabs) 27 | - strict mode 28 | - "Attractive" 29 | -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function (grunt) { 4 | "use strict"; 5 | 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON('package.json') 8 | , jshint: { 9 | all: [ 10 | "Gruntfile.js" 11 | , "lib/**/*.js" 12 | , "spec/**/*.js" 13 | ] 14 | , options: { 15 | jshintrc: '.jshintrc' 16 | }, 17 | } 18 | , jasmine: { 19 | src: "lib/**/*.js" 20 | , options: { 21 | specs: "spec/**/*.js" 22 | , vendor: "vendor/**/*.js" 23 | } 24 | } 25 | }) 26 | 27 | grunt.loadNpmTasks('grunt-contrib-jshint') 28 | grunt.loadNpmTasks('grunt-contrib-jasmine') 29 | 30 | grunt.registerTask('test', ['jshint', 'jasmine']) 31 | grunt.registerTask('default', ['test']) 32 | }; 33 | -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, 2011, 2012 Wojciech Zawistowski, Travis Jeffery 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-jquery", 3 | "version": "1.7.0", 4 | "main": "lib/jasmine-jquery.js", 5 | "ignore": [ 6 | "test", 7 | "vendor", 8 | ".*", 9 | "HISTORY.md", 10 | "SpecRunner.html" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-jquery" 3 | , "description": "jQuery matchers and fixture loader for Jasmine framework" 4 | , "version": "1.7.0" 5 | , "keywords": ["jasmine", "jquery"] 6 | , "homepage": "http://github.com/velesin/jasmine-jquery" 7 | , "author": { 8 | "name": "Travis Jeffery" 9 | , "email": "tj@travisjeffery.com" 10 | , "url": "travisjeffery.com" 11 | } 12 | , "scripts": { "test": "grunt test" } 13 | , "repository": { 14 | "type": "git" 15 | , "url": "https://github.com/velesin/jasmine-jquery.git" 16 | } 17 | , "bugs": { 18 | "url": "http://github.com/velesin/jasmine-jquery/issues" 19 | } 20 | , "license": "MIT" 21 | , "devDependencies": { 22 | "grunt": "~0.4.1" 23 | , "grunt-contrib-jshint": "~0.6.0" 24 | , "grunt-contrib-jasmine": "~0.4.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/spec/fixtures/json/jasmine_json_test.json: -------------------------------------------------------------------------------- 1 | [1,2,3] 2 | -------------------------------------------------------------------------------- /bower_components/jasmine-jquery/spec/fixtures/real_non_mocked_fixture.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /bower_components/jasmine-jquery/spec/fixtures/real_non_mocked_fixture_style.css: -------------------------------------------------------------------------------- 1 | body { background: red; } -------------------------------------------------------------------------------- /bower_components/jquery/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "version": "1.8.3", 4 | "main": "./jquery.js", 5 | "dependencies": {}, 6 | "_id": "jquery@1.8.3", 7 | "readme": "ERROR: No README.md file found!", 8 | "description": "ERROR: No README.md file found!", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/components/jquery.git" 12 | } 13 | } -------------------------------------------------------------------------------- /bower_components/jquery/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components/jquery", 3 | "description": "jQuery JavaScript Library", 4 | "type": "component", 5 | "homepage": "http://jquery.com", 6 | "license": "MIT", 7 | "support": { 8 | "irc": "irc://irc.freenode.org/jquery", 9 | "issues": "http://bugs.jquery.com", 10 | "forum": "http://forum.jquery.com", 11 | "wiki": "http://docs.jquery.com/", 12 | "source": "https://github.com/jquery/jquery" 13 | }, 14 | "authors": [ 15 | { 16 | "name": "John Resig", 17 | "email": "jeresig@gmail.com" 18 | } 19 | ], 20 | "extra": { 21 | "js": "jquery.js" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flight Mail 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 | 30 |
31 | 32 |
33 | 34 |
35 |
36 | 37 | 38 | 39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file 2 | // 3 | // For all available config options and default values, see: 4 | // https://github.com/karma-runner/karma/blob/stable/lib/config.js#L54 5 | 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath = ''; 9 | 10 | // list of files / patterns to load in the browser 11 | files = [ 12 | 'bower_components/es5-shim/es5-shim.js', 13 | 'bower_components/es5-shim/es5-sham.js', 14 | 15 | // frameworks 16 | JASMINE, 17 | JASMINE_ADAPTER, 18 | REQUIRE, 19 | REQUIRE_ADAPTER, 20 | 21 | // loaded without require 22 | 'bower_components/jquery/jquery.js', 23 | 'bower_components/jasmine-jquery/lib/jasmine-jquery.js', 24 | 'bower_components/jasmine-flight/lib/jasmine-flight.js', 25 | 26 | // loaded with require 27 | {pattern: 'bower_components/flight/**/*.js', included: false}, 28 | {pattern: 'bower_components/mustache/**/*.js', included: false}, 29 | {pattern: 'app/**/*.js', included: false}, 30 | {pattern: 'test/fixtures/**/*.html', included: false}, 31 | {pattern: 'test/spec/**/*.js', included: false}, 32 | 33 | 'test/test-main.js' 34 | ]; 35 | 36 | // list of files to exclude 37 | exclude = [ 38 | 39 | ]; 40 | 41 | // use dots reporter, as travis terminal does not support escaping sequences 42 | reporters = [ 43 | 'dots' 44 | ]; 45 | 46 | // enable / disable watching file and executing tests whenever any file changes 47 | // CLI --auto-watch --no-auto-watch 48 | autoWatch = true; 49 | 50 | // start these browsers 51 | browsers = [ 52 | 'Chrome' 53 | ]; 54 | 55 | // auto run tests on start (when browsers are captured) and exit 56 | // CLI --single-run --no-single-run 57 | singleRun = false; 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "flight-example-app", 4 | "version": "0.0.0", 5 | "devDependencies": { 6 | "karma": "~0.8.5" 7 | }, 8 | "scripts": { 9 | "test": "./node_modules/.bin/karma start --single-run --browsers Firefox" 10 | }, 11 | "dependencies": { 12 | "karma": "^0.12.31" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /requireMain.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | baseUrl: '', 3 | paths: { 4 | 'flight': 'bower_components/flight' 5 | } 6 | }); 7 | 8 | require( 9 | [ 10 | 'flight/lib/debug' 11 | ], 12 | 13 | function(debug) { 14 | debug.enable(true); 15 | require(['app/boot/page'], function(initialize) { 16 | initialize(); 17 | }); 18 | } 19 | ); 20 | -------------------------------------------------------------------------------- /test/fixtures/compose_box.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/mail_controls.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
-------------------------------------------------------------------------------- /test/fixtures/mail_items.html: -------------------------------------------------------------------------------- 1 | 2 |   3 | bill 4 | Consectetur adipiscing elit 5 | - Vestibulum vestibulum 6 | 7 | 8 | Important 9 | jane 10 | Neque porro quisquam velit!! 11 | - Curabitur sollicitudin 12 | 13 | 14 | Important 15 | bob 16 | Proin egestas aliquam :) 17 | - Aenean nec erat id 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/move_to_selector.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
-------------------------------------------------------------------------------- /test/spec/component_data/compose_box_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_data/compose_box', function() { 4 | beforeEach(function() { 5 | setupComponent( 6 | { 7 | recipientHintId: "abc", 8 | dataStore: { 9 | contacts: [ 10 | {"id": "contact_3"}, 11 | {"id": "contact_5"} 12 | ], 13 | mail: [ 14 | {id: "mail_1", contact_id: "contact_3", subject:"blah", message: "blugh", folders: ['inbox']}, 15 | {id: "mail_2", contact_id: "contact_5", subject:"blee", message: "blooo", folders: ['later']} 16 | ] 17 | } 18 | } 19 | ); 20 | }); 21 | 22 | it('serves compose box when requested', function() { 23 | spyOnEvent(document, 'dataComposeBoxServed'); 24 | this.component.trigger('uiComposeBoxRequested', {}); 25 | expect('dataComposeBoxServed').toHaveBeenTriggeredOn(document); 26 | }); 27 | 28 | it('returns the contact_id when getRecipientId is passed a relatedMailId', function() { 29 | expect(this.component.getRecipientId('reply', 'mail_1')).toBe('contact_3'); 30 | }); 31 | 32 | it('returns the recipientHintId when getRecipientId is not passed a relatedMailId', function() { 33 | expect(this.component.getRecipientId('reply')).toBe(this.component.attr.recipientHintId); 34 | }); 35 | 36 | it('sends email when requested', function() { 37 | var dataMailItemsRefreshRequested = spyOnEvent(document, 'dataMailItemsRefreshRequested'); 38 | this.component.trigger('uiSendRequested', {to_id: 'contact_9', subject: 'b', message: 'c'}); 39 | expect('dataMailItemsRefreshRequested').toHaveBeenTriggeredOn(document); 40 | var newMail = this.component.attr.dataStore.mail.filter(function(e) {return e.contact_id == 'contact_9'})[0]; 41 | expect(newMail).toBeDefined(); 42 | expect(newMail.contact_id).toBe('contact_9'); 43 | expect(newMail.subject).toBe('b'); 44 | expect(newMail.message).toBe('c'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/spec/component_data/mail_items_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_data/mail_items', function() { 4 | beforeEach(function() { 5 | setupComponent( 6 | { 7 | dataStore: { 8 | contacts: [ 9 | {"id": "contact_3"}, 10 | {"id": "contact_5"} 11 | ], 12 | mail: [ 13 | {id: "mail_1", contact_id: "contact_3", subject:"blah", message: "blugh", folders: ['inbox']}, 14 | {id: "mail_2", contact_id: "contact_5", subject:"blee", message: "blooo", folders: ['later']} 15 | ] 16 | } 17 | } 18 | ); 19 | }); 20 | 21 | it('serves mail items when requested', function() { 22 | spyOnEvent(document, 'dataMailItemsServed'); 23 | this.component.trigger('uiMailItemsRequested', {folder: 'inbox'}); 24 | expect('dataMailItemsServed').toHaveBeenTriggeredOn(document); 25 | }); 26 | 27 | it('should collate items for given folder when assembleItems is invoked with folder', function() { 28 | var items = this.component.assembleItems('inbox'); 29 | expect(items.length).toBe(1); 30 | items = this.component.assembleItems('sent'); 31 | expect(items.length).toBe(0); 32 | items = this.component.assembleItems('later'); 33 | expect(items.length).toBe(1); 34 | }); 35 | 36 | it('serves mail items after refresh', function() { 37 | spyOnEvent(document, 'dataMailItemsServed'); 38 | this.component.trigger('dataMailItemsRefreshRequested', {}); 39 | expect('dataMailItemsServed').toHaveBeenTriggeredOn(document); 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/component_data/move_to_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_data/move_to', function() { 4 | beforeEach(function() { 5 | setupComponent( 6 | { 7 | recipientHintId: 'abc', 8 | dataStore: { 9 | folders: ["inbox", "later", "sent", "trash"], 10 | contacts: [ 11 | {"id": "contact_3"}, 12 | {"id": "contact_5"} 13 | ], 14 | mail: [ 15 | {id: "mail_1", contact_id: "contact_3", subject:"blah", message: "blugh", folders: ['inbox']}, 16 | {id: "mail_2", contact_id: "contact_5", subject:"blee", message: "blooo", folders: ['later']} 17 | ] 18 | } 19 | } 20 | ); 21 | }); 22 | 23 | it('serves move to items when requested', function() { 24 | spyOnEvent(document, 'dataMoveToItemsServed'); 25 | this.component.trigger('uiAvailableFoldersRequested', {folder: 'inbox'}); 26 | expect('dataMoveToItemsServed').toHaveBeenTriggeredOn(document); 27 | }); 28 | 29 | it('moves requested items', function() { 30 | var mailId = 'mail_1'; 31 | var movedMail = this.component.attr.dataStore.mail.filter(function(e) {return e.id == mailId})[0]; 32 | expect(movedMail.folders[0]).toBe('inbox'); 33 | this.component.trigger('uiMoveItemsRequested', {itemIds: [mailId], toFolder: 'later'}); 34 | expect(movedMail.folders[0]).toBe('later'); 35 | }); 36 | 37 | it('refreshes mail items after move', function() { 38 | spyOnEvent(document, 'dataMailItemsRefreshRequested'); 39 | this.component.trigger('uiMoveItemsRequested', {itemIds: ["mail_2"], toFolder: 'later'}); 40 | expect('dataMailItemsRefreshRequested').toHaveBeenTriggeredOn(document); 41 | }); 42 | 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /test/spec/component_ui/compose_box_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_ui/compose_box', function() { 4 | 5 | beforeEach(function() { 6 | setupComponent( 7 | readFixtures('compose_box.html'), 8 | { 9 | newMailType: 'newMail', 10 | forwardMailType: 'forward', 11 | replyMailType: 'reply', 12 | newControlSelector: '#new_mail', 13 | cancelSelector: '#cancel_composed', 14 | sendSelector: '#send_composed', 15 | recipientSelector: '#recipient_select' 16 | } 17 | ); 18 | }); 19 | 20 | it('shows compose box when compose box data is served', function() { 21 | this.component.trigger('dataComposeBoxServed', {type: 'anything'}); 22 | expect(this.component.$node).toBeVisible(); 23 | }); 24 | 25 | it('hides compose box when cancel is triggered', function() { 26 | this.component.trigger(this.component.attr.cancelSelector, 'click'); 27 | expect(this.component.$node).not.toBeVisible(); 28 | }); 29 | 30 | it('requests appropriate compose box when new mail is triggered', function() { 31 | var uiComposeBoxRequested = spyOnEvent(document, 'uiComposeBoxRequested'); 32 | this.component.trigger(this.component.attr.newControlSelector, 'click'); 33 | expect('uiComposeBoxRequested').toHaveBeenTriggeredOn(document); 34 | expect(uiComposeBoxRequested.mostRecentCall.data.type).toEqual(this.component.attr.newMailType); 35 | }); 36 | 37 | it('requests appropriate compose box when forward is triggered', function() { 38 | var uiComposeBoxRequested = spyOnEvent(document, 'uiComposeBoxRequested'); 39 | this.component.trigger('uiForwardMail'); 40 | expect('uiComposeBoxRequested').toHaveBeenTriggeredOn(document); 41 | expect(uiComposeBoxRequested.mostRecentCall.data.type).toEqual(this.component.attr.forwardMailType); 42 | }); 43 | 44 | it('requests appropriate compose box when reply is triggered', function() { 45 | var uiComposeBoxRequested = spyOnEvent(document, 'uiComposeBoxRequested'); 46 | this.component.trigger('uiReplyToMail'); 47 | expect('uiComposeBoxRequested').toHaveBeenTriggeredOn(document); 48 | expect(uiComposeBoxRequested.mostRecentCall.data.type).toEqual(this.component.attr.replyMailType); 49 | }); 50 | 51 | it('triggers send request when send selected', function() { 52 | spyOnEvent(document, 'uiSendRequested'); 53 | this.component.trigger(this.component.attr.sendSelector, 'click'); 54 | expect('uiSendRequested').toHaveBeenTriggeredOn(document); 55 | }); 56 | 57 | it('hides compose box when send selected', function() { 58 | this.component.trigger(this.component.attr.sendSelector, 'click'); 59 | expect(this.component.$node).not.toBeVisible(); 60 | }); 61 | 62 | it('enables send when recipient is changed', function() { 63 | this.component.trigger(this.component.attr.recipientSelector, 'change'); 64 | expect(this.component.select('sendSelector')).not.toBeDisabled(); 65 | }); 66 | 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /test/spec/component_ui/folders_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_ui/folders', function() { 4 | beforeEach(function() { 5 | setupComponent(); 6 | }); 7 | 8 | it('should listen to uiFolderSelectionChanged and trigger fetchMailItems', function() { 9 | var uiMailItemsRequested = spyOnEvent(document, 'uiMailItemsRequested'); 10 | this.component.trigger('uiFolderSelectionChanged', {selectedIds: [2, 3, 4]}); 11 | expect('uiMailItemsRequested').toHaveBeenTriggeredOn(document); 12 | expect(uiMailItemsRequested.mostRecentCall.data).toEqual({ 13 | folder: 2 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/spec/component_ui/mail_controls_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_ui/mail_controls', function() { 4 | beforeEach(function () { 5 | setupComponent(readFixtures('mail_controls.html')); 6 | }); 7 | 8 | it('should trigger appropriate event when delete control clicked', function() { 9 | spyOnEvent(document, 'uiDeleteMail'); 10 | this.component.trigger(this.component.select('deleteControlSelector'), 'click'); 11 | expect('uiDeleteMail').toHaveBeenTriggeredOn(document); 12 | }); 13 | 14 | 15 | it('should trigger appropriate event when move control clicked', function() { 16 | spyOnEvent(document, 'uiMoveMail'); 17 | this.component.trigger(this.component.select('moveControlSelector'), 'click'); 18 | expect('uiMoveMail').toHaveBeenTriggeredOn(document); 19 | }); 20 | 21 | 22 | it('should trigger appropriate event when forward control clicked', function() { 23 | spyOnEvent(document, 'uiForwardMail'); 24 | this.component.trigger(this.component.select('forwardControlSelector'), 'click'); 25 | expect('uiForwardMail').toHaveBeenTriggeredOn(document); 26 | }); 27 | 28 | 29 | it('should trigger appropriate event when reply to control clicked', function() { 30 | spyOnEvent(document, 'uiReplyToMail'); 31 | this.component.trigger(this.component.select('replyControlSelector'), 'click'); 32 | expect('uiReplyToMail').toHaveBeenTriggeredOn(document); 33 | }); 34 | 35 | it('should not enable any buttons when no items are selected', function() { 36 | this.component.trigger('uiMailItemSelectionChanged', {selectedIds:[]}); 37 | var selected = this.component.select('actionControlsSelector').filter(function() { 38 | return this.getAttribute('disabled') == null; 39 | }); 40 | expect(selected.length).toBe(0); 41 | }); 42 | 43 | it('should enable all buttons when exactly one item is selected', function() { 44 | this.component.trigger('uiMailItemSelectionChanged', {selectedIds:[0]}); 45 | var selected = this.component.select('actionControlsSelector').filter(function() { 46 | return this.getAttribute('disabled') == 'disabled'; 47 | }); 48 | expect(selected.length).toBe(0); 49 | }); 50 | 51 | it('should enable delete and move when more than one item is selected', function() { 52 | this.component.trigger('uiMailItemSelectionChanged', {selectedIds:[0, 1]}); 53 | expect(this.component.select('deleteControlSelector')).not.toBeDisabled(); 54 | expect(this.component.select('moveControlSelector')).not.toBeDisabled(); 55 | }); 56 | 57 | it('should not enable fwd and reply when more than one item is selected', function() { 58 | this.component.trigger('uiMailItemSelectionChanged', {selectedIds:[0, 1]}); 59 | expect(this.component.select('forwardControlSelector')).toBeDisabled(); 60 | expect(this.component.select('replyControlSelector')).toBeDisabled(); 61 | }); 62 | 63 | it('should disbale all buttons when folder is selected', function() { 64 | this.component.trigger('uiFolderSelectionChanged'); 65 | var selected = this.component.select('actionControlsSelector').filter(function() { 66 | return this.getAttribute('disabled') == null; 67 | }); 68 | expect(selected.length).toBe(0); 69 | }); 70 | 71 | 72 | 73 | 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /test/spec/component_ui/mail_items_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_ui/mail_items', function() { 4 | beforeEach(function() { 5 | setupComponent('
', { 6 | itemContainerSelector: '#container-tb', 7 | deleteFolder: 'delete', 8 | selectedFolders: ['inbox'], 9 | selectedMailItems: [2, 3]}); 10 | }); 11 | 12 | it('should render mail data in items container', function() { 13 | this.component.attr.itemContainerSelector = '#container-tb'; 14 | this.component.trigger('dataMailItemsServed', {markup: readFixtures('mail_items.html')}); 15 | expect(this.component.select('itemContainerSelector').find('tr').length).toBe(3); 16 | }); 17 | 18 | it('should trigger unselect all when rendering mail', function() { 19 | var uiMailItemSelectionChanged = spyOnEvent(document, 'uiMailItemSelectionChanged'); 20 | this.component.trigger('dataMailItemsServed', {markup: readFixtures('mail_items.html')}); 21 | expect('uiMailItemSelectionChanged').toHaveBeenTriggeredOn(document); 22 | expect(uiMailItemSelectionChanged.mostRecentCall.data).toEqual({ 23 | selectedIds: [] 24 | }); 25 | }); 26 | 27 | it('should request selections to move to trash when delete triggered', function() { 28 | var uiMoveItemsRequested = spyOnEvent(document, 'uiMoveItemsRequested'); 29 | this.component.trigger('uiDeleteMail'); 30 | expect('uiMoveItemsRequested').toHaveBeenTriggeredOn(document); 31 | expect(uiMoveItemsRequested.mostRecentCall.data).toEqual({ 32 | itemIds: [2, 3], 33 | fromFolder: 'inbox', 34 | toFolder: 'delete' 35 | }); 36 | }); 37 | 38 | it('should update mail selections', function() { 39 | this.component.trigger('uiMailItemSelectionChanged', {selectedIds: [4, 5]}); 40 | expect(this.component.attr.selectedMailItems).toEqual([4, 5]); 41 | }); 42 | 43 | it('should update folder selections', function() { 44 | this.component.trigger('uiFolderSelectionChanged', {selectedIds: ['sent']}); 45 | expect(this.component.attr.selectedFolders).toEqual(['sent']); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/spec/component_ui/move_to_selector_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeComponent('app/component_ui/move_to_selector', function() { 4 | beforeEach(function () { 5 | setupComponent(readFixtures('move_to_selector.html'), { 6 | moveActionSelector: '#move_mail' 7 | }); 8 | }); 9 | 10 | it('asks for available folders when move is requested', function() { 11 | spyOnEvent(document, 'uiAvailableFoldersRequested'); 12 | this.component.trigger('uiMoveMail'); 13 | expect('uiAvailableFoldersRequested').toHaveBeenTriggeredOn(document); 14 | }); 15 | 16 | it('launches move selector when available folders are served', function() { 17 | this.component.trigger('dataMoveToItemsServed', {markup: '
'}); 18 | expect(this.component.$node).toBeVisible(); 19 | }); 20 | 21 | it('hides launched selector after click', function() { 22 | this.component.launchSelector(null, {markup: '
'}) 23 | window.setTimeout(function() { 24 | this.component.trigger('click'); 25 | expect(this.component.$node).not.toBeVisible(); 26 | }.bind(this),1000); 27 | }); 28 | 29 | it('requests move when folder selected', function() { 30 | var uiMoveItemsRequested = spyOnEvent(document, 'uiMoveItemsRequested'); 31 | this.component.trigger('uiMoveToSelectionChanged', {selectedIds: [96]}); 32 | expect('uiMoveItemsRequested').toHaveBeenTriggeredOn(document); 33 | expect(uiMoveItemsRequested.mostRecentCall.data.toFolder).toEqual(96); 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /test/spec/component_ui/with_select_spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describeMixin('app/component_ui/with_select', function() { 4 | beforeEach(function() { 5 | setupComponent($('' + readFixtures('mail_items.html') + '
'), { 6 | itemSelector: 'tr.mail-item', 7 | selectedItemSelector: 'tr.mail-item.selected', 8 | selectionChangedEvent: 'testSelectionChangedEvent', 9 | selectedClass: 'selected' 10 | }); 11 | }); 12 | 13 | it('appends selection to the selectionItem cache', function() { 14 | var $selectedElem = $('#mail_1'); 15 | this.component.selectItem($selectedElem); 16 | expect(this.component.getSelectedIds().length).toBe(1); 17 | }); 18 | 19 | it('adds the selected class to the selected item', function() { 20 | var $selectedElem = $('#mail_2'); 21 | this.component.selectItem($selectedElem); 22 | expect($selectedElem).toBe('.' + this.component.attr.selectedClass); 23 | }); 24 | 25 | it('it triggers selectionChangedEvent on selection', function() { 26 | var $selectedElem = $('#mail_2139'); 27 | var testSelectionChangedEvent = spyOnEvent(document, 'testSelectionChangedEvent'); 28 | this.component.selectItem($selectedElem); 29 | expect('testSelectionChangedEvent').toHaveBeenTriggeredOn(document); 30 | expect(testSelectionChangedEvent.mostRecentCall.data).toEqual({ 31 | selectedIds: this.component.getSelectedIds() 32 | }); 33 | }); 34 | 35 | it('removes unselected item from the selectionItem cache', function() { 36 | var $unselectedElem = $('#mail_1'); 37 | this.component.attr.selectedIds = ['#mail_1'] 38 | this.component.unselectItem($unselectedElem); 39 | expect(this.component.getSelectedIds().length).toBe(0); 40 | }); 41 | 42 | it('removes the selected class from the unselected item', function() { 43 | var $unselectedElem = $('#mail_1'); 44 | $unselectedElem.addClass(this.component.attr.selectedClass); 45 | this.component.unselectItem($unselectedElem); 46 | expect($unselectedElem).not.toBe('.' + this.component.attr.selectedClass); 47 | }); 48 | 49 | it('triggers selectionChangedEvent on unselection', function() { 50 | var $unselectedElem = $('#mail_1'); 51 | var testSelectionChangedEvent = spyOnEvent(document, 'testSelectionChangedEvent'); 52 | this.component.selectItem($unselectedElem); 53 | expect('testSelectionChangedEvent').toHaveBeenTriggeredOn(document); 54 | expect(testSelectionChangedEvent.mostRecentCall.data).toEqual({ 55 | selectedIds: this.component.getSelectedIds() 56 | }); 57 | }); 58 | 59 | it('unselects when toggleItemSelect is called on a selected item', function() { 60 | var $selectedElem = $('#mail_1'); 61 | this.component.attr.selectedIds = ['#mail_1']; 62 | $selectedElem.addClass(this.component.attr.selectedClass); 63 | this.component.toggleItemSelect(null, {el: $selectedElem}); 64 | expect(this.component.getSelectedIds().length).toBe(0); 65 | expect($selectedElem).not.toBe('.' + this.component.attr.selectedClass); 66 | }); 67 | 68 | it('selects when toggleItemSelect is called on an unselected item', function() { 69 | var $selectedElem = $('#mail_1'); 70 | this.component.toggleItemSelect(null, {el: $selectedElem}); 71 | expect(this.component.getSelectedIds().length).toBe(1); 72 | expect($selectedElem).toBe('.' + this.component.attr.selectedClass); 73 | }); 74 | 75 | it('adds selected items to selectedIds when initializeSelections called', function() { 76 | $('#mail_1').addClass(this.component.attr.selectedClass); 77 | $('#mail_2').addClass(this.component.attr.selectedClass); 78 | debugger; 79 | this.component.initializeSelections(); 80 | expect(this.component.getSelectedIds()).toEqual(['mail_1', 'mail_2']); 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /test/test-main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var tests = Object.keys(window.__karma__.files).filter(function (file) { 4 | return (/_spec\.js$/.test(file)); 5 | }); 6 | 7 | requirejs.config({ 8 | // Karma serves files from '/base' 9 | baseUrl: '/base', 10 | 11 | paths: { 12 | 'flight': 'bower_components/flight' 13 | }, 14 | 15 | // ask Require.js to load these files (all our tests) 16 | deps: tests, 17 | 18 | // start test run, once Require.js is done 19 | callback: window.__karma__.start 20 | }); 21 | 22 | jasmine.getFixtures().fixturesPath = 'base/test/fixtures'; 23 | --------------------------------------------------------------------------------