mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-11-13 08:44:12 +03:00
Merge branch 'view'
This commit is contained in:
commit
0fe85355f8
32
spec/atom/file-finder-spec.coffee
Normal file
32
spec/atom/file-finder-spec.coffee
Normal file
@ -0,0 +1,32 @@
|
||||
FileFinder = require 'file-finder'
|
||||
|
||||
describe 'FileFinder', ->
|
||||
finder = null
|
||||
|
||||
beforeEach ->
|
||||
urls = ['app.coffee', 'buffer.coffee', 'atom/app.coffee', 'atom/buffer.coffee']
|
||||
finder = FileFinder.build {urls}
|
||||
|
||||
describe "when characters are typed into the input element", ->
|
||||
it "displays matching urls in the ol element", ->
|
||||
expect(finder.urlList.find('li')).not.toExist()
|
||||
|
||||
finder.input.text('ap')
|
||||
finder.input.keypress()
|
||||
|
||||
expect(finder.urlList.children().length).toBe 2
|
||||
expect(finder.urlList.find('li:contains(app.coffee)').length).toBe 2
|
||||
expect(finder.urlList.find('li:contains(atom/app.coffee)').length).toBe 1
|
||||
|
||||
# we should clear the list before re-populating it
|
||||
finder.input.text('a/ap')
|
||||
finder.input.keypress()
|
||||
|
||||
expect(finder.urlList.children().length).toBe 1
|
||||
expect(finder.urlList.find('li:contains(atom/app.coffee)').length).toBe 1
|
||||
|
||||
describe "findMatches(queryString)", ->
|
||||
it "returns urls sorted by score of match against the given query", ->
|
||||
expect(finder.findMatches('ap')).toEqual ["app.coffee", "atom/app.coffee"]
|
||||
expect(finder.findMatches('a/ap')).toEqual ["atom/app.coffee"]
|
||||
|
@ -1,3 +1,4 @@
|
||||
nakedLoad 'jasmine-jquery'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
Native = require 'native'
|
||||
|
@ -3,18 +3,64 @@ Template = require 'template'
|
||||
describe "Template", ->
|
||||
describe "toView", ->
|
||||
Foo = null
|
||||
view = null
|
||||
|
||||
beforeEach ->
|
||||
class Foo extends Template
|
||||
content: ->
|
||||
div ->
|
||||
h1 @title
|
||||
content: (attrs) ->
|
||||
@div =>
|
||||
@h1 attrs.title
|
||||
@list()
|
||||
|
||||
list: ->
|
||||
@ol =>
|
||||
@li outlet: 'li1', click: 'li1Clicked', class: 'foo', "one"
|
||||
@li outlet: 'li2', keypress:'li2Keypressed', class: 'bar', "two"
|
||||
|
||||
viewProperties:
|
||||
initialize: (attrs) ->
|
||||
@initializeCalledWith = attrs
|
||||
foo: "bar",
|
||||
li1Clicked: ->,
|
||||
li2Keypressed: ->
|
||||
|
||||
view = Foo.build(title: "Zebra")
|
||||
|
||||
afterEach ->
|
||||
delete window.Foo
|
||||
|
||||
it "builds a jquery object based on the content method and extends it with the viewProperties", ->
|
||||
view = Foo.buildView(title: "Hello World")
|
||||
expect(view.find('h1').text()).toEqual "Hello World"
|
||||
describe ".build(attributes)", ->
|
||||
it "generates markup based on the content method", ->
|
||||
expect(view).toMatchSelector "div"
|
||||
expect(view.find("h1:contains(Zebra)")).toExist()
|
||||
expect(view.find("ol > li.foo:contains(one)")).toExist()
|
||||
expect(view.find("ol > li.bar:contains(two)")).toExist()
|
||||
|
||||
it "extends the view with viewProperties, calling the 'constructor' property if present", ->
|
||||
expect(view.constructor).toBeDefined()
|
||||
expect(view.foo).toBe("bar")
|
||||
expect(view.initializeCalledWith).toEqual title: "Zebra"
|
||||
|
||||
it "wires references for elements with 'outlet' attributes", ->
|
||||
expect(view.li1).toMatchSelector "li.foo:contains(one)"
|
||||
expect(view.li2).toMatchSelector "li.bar:contains(two)"
|
||||
|
||||
it "binds events for elements with event name attributes", ->
|
||||
spyOn(view, 'li1Clicked').andCallFake (event, elt) ->
|
||||
expect(event.type).toBe 'click'
|
||||
expect(elt).toMatchSelector 'li.foo:contains(one)'
|
||||
|
||||
spyOn(view, 'li2Keypressed').andCallFake (event, elt) ->
|
||||
expect(event.type).toBe 'keypress'
|
||||
expect(elt).toMatchSelector "li.bar:contains(two)"
|
||||
|
||||
view.li1.click()
|
||||
expect(view.li1Clicked).toHaveBeenCalled()
|
||||
expect(view.li2Keypressed).not.toHaveBeenCalled()
|
||||
|
||||
view.li1Clicked.reset()
|
||||
|
||||
view.li2.keypress()
|
||||
expect(view.li2Keypressed).toHaveBeenCalled()
|
||||
expect(view.li1Clicked).not.toHaveBeenCalled()
|
||||
|
||||
|
41
spec/stdlib/template/builder-spec.coffee
Normal file
41
spec/stdlib/template/builder-spec.coffee
Normal file
@ -0,0 +1,41 @@
|
||||
Builder = require 'template/builder'
|
||||
|
||||
describe "Builder", ->
|
||||
builder = null
|
||||
|
||||
beforeEach -> builder = new Builder
|
||||
|
||||
describe ".tag(name, args...)", ->
|
||||
it "can generate simple tags", ->
|
||||
builder.tag 'div'
|
||||
expect(builder.toHtml()).toBe "<div></div>"
|
||||
|
||||
builder.reset()
|
||||
builder.tag 'ol'
|
||||
expect(builder.toHtml()).toBe "<ol></ol>"
|
||||
|
||||
it "can generate tags with content", ->
|
||||
builder.tag 'ol', ->
|
||||
builder.tag 'li'
|
||||
builder.tag 'li'
|
||||
|
||||
expect(builder.toHtml()).toBe "<ol><li></li><li></li></ol>"
|
||||
|
||||
it "can generate tags with text", ->
|
||||
builder.tag 'div', "hello"
|
||||
expect(builder.toHtml()).toBe "<div>hello</div>"
|
||||
|
||||
builder.reset()
|
||||
builder.tag 'div', 22
|
||||
expect(builder.toHtml()).toBe "<div>22</div>"
|
||||
|
||||
it "can generate tags with attributes", ->
|
||||
builder.tag 'div', id: 'foo', class: 'bar'
|
||||
fragment = builder.toFragment()
|
||||
expect(fragment.attr('id')).toBe 'foo'
|
||||
expect(fragment.attr('class')).toBe 'bar'
|
||||
|
||||
it "can generate self-closing tags", ->
|
||||
builder.tag 'br', id: 'foo'
|
||||
expect(builder.toHtml()).toBe '<br id="foo">'
|
||||
|
26
src/atom/file-finder.coffee
Normal file
26
src/atom/file-finder.coffee
Normal file
@ -0,0 +1,26 @@
|
||||
$ = require 'jquery'
|
||||
Template = require 'template'
|
||||
stringScore = require 'stringscore'
|
||||
|
||||
module.exports =
|
||||
class FileFinder extends Template
|
||||
content: ->
|
||||
@div class: 'file-finder', =>
|
||||
@ol outlet: 'urlList'
|
||||
@input outlet: 'input', keypress: 'populateUrlList'
|
||||
|
||||
viewProperties:
|
||||
urls: null
|
||||
|
||||
initialize: ({@urls}) ->
|
||||
|
||||
populateUrlList: ->
|
||||
@urlList.empty()
|
||||
for url in @findMatches(@input.text())
|
||||
@urlList.append $("<li>#{url}</li>")
|
||||
|
||||
findMatches: (query) ->
|
||||
scoredUrls = ({url, score: stringScore(url, query)} for url in @urls)
|
||||
sortedUrls = scoredUrls.sort (a, b) -> a.score > b.score
|
||||
urlAndScore.url for urlAndScore in sortedUrls when urlAndScore.score > 0
|
||||
|
@ -4,13 +4,13 @@ Template = require 'template'
|
||||
module.exports =
|
||||
class Layout extends Template
|
||||
@attach: ->
|
||||
view = @buildView()
|
||||
view = @build()
|
||||
$('body').append(view)
|
||||
view
|
||||
|
||||
content: ->
|
||||
link rel: 'stylesheet', href: 'static/atom.css'
|
||||
div id: 'app-horizontal', ->
|
||||
div id: 'app-vertical', ->
|
||||
div id: 'main'
|
||||
@link rel: 'stylesheet', href: 'static/atom.css'
|
||||
@div id: 'app-horizontal', =>
|
||||
@div id: 'app-vertical', =>
|
||||
@div id: 'main'
|
||||
|
||||
|
@ -1,10 +1,43 @@
|
||||
$ = require 'jquery'
|
||||
coffeekup = require 'coffeekup'
|
||||
_ = require 'underscore'
|
||||
Builder = require 'template/builder'
|
||||
|
||||
module.exports =
|
||||
class Template
|
||||
@buildView: (attributes) ->
|
||||
(new this).buildView(attributes)
|
||||
@events: 'blur change click dblclick error focus keydown
|
||||
keypress keyup load mousedown mousemove mouseout mouseover
|
||||
mouseup resize scroll select submit unload'.split /\s+/
|
||||
|
||||
@buildTagMethod: (name) ->
|
||||
this.prototype[name] = (args...) -> @builder.tag(name, args...)
|
||||
|
||||
@buildTagMethod(name) for name in Builder.elements.normal
|
||||
@buildTagMethod(name) for name in Builder.elements.void
|
||||
|
||||
@build: (attributes) ->
|
||||
(new this).build(attributes)
|
||||
|
||||
build: (attributes) ->
|
||||
@builder = new Builder
|
||||
@content(attributes)
|
||||
view = @builder.toFragment()
|
||||
@wireOutlets(view)
|
||||
@bindEvents(view)
|
||||
if @viewProperties
|
||||
$.extend(view, @viewProperties)
|
||||
view.initialize?(attributes)
|
||||
view
|
||||
|
||||
wireOutlets: (view) ->
|
||||
view.find('[outlet]').each ->
|
||||
elt = $(this)
|
||||
outletName = elt.attr('outlet')
|
||||
view[outletName] = elt
|
||||
|
||||
bindEvents: (view) ->
|
||||
for eventName in this.constructor.events
|
||||
view.find("[#{eventName}]").each ->
|
||||
elt = $(this)
|
||||
methodName = elt.attr(eventName)
|
||||
elt[eventName]((event) -> view[methodName](event, elt))
|
||||
|
||||
buildView: (attributes) ->
|
||||
$(coffeekup.render(@content, attributes))
|
||||
|
60
src/stdlib/template/builder.coffee
Normal file
60
src/stdlib/template/builder.coffee
Normal file
@ -0,0 +1,60 @@
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
OpenTag = require 'template/open-tag'
|
||||
CloseTag = require 'template/close-tag'
|
||||
Text = require 'template/text'
|
||||
|
||||
module.exports =
|
||||
class Builder
|
||||
@elements:
|
||||
normal: 'a abbr address article aside audio b bdi bdo blockquote body button
|
||||
canvas caption cite code colgroup datalist dd del details dfn div dl dt em
|
||||
fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup
|
||||
html i iframe ins kbd label legend li map mark menu meter nav noscript object
|
||||
ol optgroup option output p pre progress q rp rt ruby s samp script section
|
||||
select small span strong style sub summary sup table tbody td textarea tfoot
|
||||
th thead time title tr u ul video'.split /\s+/
|
||||
|
||||
void: 'area base br col command embed hr img input keygen link meta param
|
||||
source track wbr'.split /\s+/
|
||||
|
||||
constructor: ->
|
||||
@reset()
|
||||
|
||||
toHtml: ->
|
||||
_.map(@document, (x) -> x.toHtml()).join('')
|
||||
|
||||
toFragment: ->
|
||||
$(@toHtml())
|
||||
|
||||
tag: (name, args...) ->
|
||||
options = @extractOptions(args)
|
||||
|
||||
@document.push(new OpenTag(name, options.attributes))
|
||||
if @elementIsVoid(name)
|
||||
if (options.text? or options.content?)
|
||||
throw new Error("Self-closing tag #{tag} cannot have text or content")
|
||||
else
|
||||
options.content?()
|
||||
@text(options.text) if options.text
|
||||
@document.push(new CloseTag(name))
|
||||
|
||||
|
||||
elementIsVoid: (name) ->
|
||||
_.contains(this.constructor.elements.void, name)
|
||||
|
||||
extractOptions: (args) ->
|
||||
options = {}
|
||||
for arg in args
|
||||
options.content = arg if _.isFunction(arg)
|
||||
options.text = arg if _.isString(arg)
|
||||
options.text = arg.toString() if _.isNumber(arg)
|
||||
options.attributes = arg if _.isObject(arg)
|
||||
options
|
||||
|
||||
text: (string) ->
|
||||
@document.push(new Text(string))
|
||||
|
||||
reset: ->
|
||||
@document = []
|
||||
|
7
src/stdlib/template/close-tag.coffee
Normal file
7
src/stdlib/template/close-tag.coffee
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports =
|
||||
class CloseTag
|
||||
constructor: (@name) ->
|
||||
|
||||
toHtml: ->
|
||||
"</#{@name}>"
|
||||
|
12
src/stdlib/template/open-tag.coffee
Normal file
12
src/stdlib/template/open-tag.coffee
Normal file
@ -0,0 +1,12 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class OpenTag
|
||||
constructor: (@name, @attributes) ->
|
||||
|
||||
toHtml: ->
|
||||
"<#{@name}#{@attributesHtml()}>"
|
||||
|
||||
attributesHtml: ->
|
||||
s = _.map(@attributes, (value, key) -> "#{key}=\"#{value}\"").join(' ')
|
||||
if s == "" then "" else " " + s
|
6
src/stdlib/template/text.coffee
Normal file
6
src/stdlib/template/text.coffee
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports =
|
||||
class Text
|
||||
constructor: (@string) ->
|
||||
|
||||
toHtml: -> @string
|
||||
|
177
vendor/jasmine-jquery.js
vendored
Normal file
177
vendor/jasmine-jquery.js
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
jQuery = require('jquery');
|
||||
jasmine.JQuery = function() {};
|
||||
|
||||
jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
|
||||
return jQuery('<div/>').append(html).html();
|
||||
};
|
||||
|
||||
jasmine.JQuery.elementToString = function(element) {
|
||||
return jQuery('<div />').append(element.clone()).html();
|
||||
};
|
||||
|
||||
jasmine.JQuery.matchersClass = {};
|
||||
|
||||
(function(namespace) {
|
||||
var data = {
|
||||
spiedEvents: {},
|
||||
handlers: []
|
||||
};
|
||||
|
||||
namespace.events = {
|
||||
spyOn: function(selector, eventName) {
|
||||
var handler = function(e) {
|
||||
data.spiedEvents[[selector, eventName]] = e;
|
||||
};
|
||||
jQuery(selector).bind(eventName, handler);
|
||||
data.handlers.push(handler);
|
||||
},
|
||||
|
||||
wasTriggered: function(selector, eventName) {
|
||||
return !!(data.spiedEvents[[selector, eventName]]);
|
||||
},
|
||||
|
||||
cleanUp: function() {
|
||||
data.spiedEvents = {};
|
||||
data.handlers = [];
|
||||
}
|
||||
}
|
||||
})(jasmine.JQuery);
|
||||
|
||||
(function(){
|
||||
var jQueryMatchers = {
|
||||
toHaveClass: function(className) {
|
||||
return this.actual.hasClass(className);
|
||||
},
|
||||
|
||||
toBeVisible: function() {
|
||||
return this.actual.is(':visible');
|
||||
},
|
||||
|
||||
toBeHidden: function() {
|
||||
return this.actual.is(':hidden');
|
||||
},
|
||||
|
||||
toBeSelected: function() {
|
||||
return this.actual.is(':selected');
|
||||
},
|
||||
|
||||
toBeChecked: function() {
|
||||
return this.actual.is(':checked');
|
||||
},
|
||||
|
||||
toBeEmpty: function() {
|
||||
return this.actual.is(':empty');
|
||||
},
|
||||
|
||||
toExist: function() {
|
||||
return this.actual.size() > 0;
|
||||
},
|
||||
|
||||
toHaveAttr: function(attributeName, expectedAttributeValue) {
|
||||
return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
|
||||
},
|
||||
|
||||
toHaveId: function(id) {
|
||||
return this.actual.attr('id') == id;
|
||||
},
|
||||
|
||||
toHaveHtml: function(html) {
|
||||
return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
|
||||
},
|
||||
|
||||
toHaveText: function(text) {
|
||||
if (text && jQuery.isFunction(text.test)) {
|
||||
return text.test(this.actual.text());
|
||||
} else {
|
||||
return this.actual.text() == text;
|
||||
}
|
||||
},
|
||||
|
||||
toHaveValue: function(value) {
|
||||
return this.actual.val() == value;
|
||||
},
|
||||
|
||||
toHaveData: function(key, expectedValue) {
|
||||
return hasProperty(this.actual.data(key), expectedValue);
|
||||
},
|
||||
|
||||
toMatchSelector: function(selector) {
|
||||
return this.actual.is(selector);
|
||||
},
|
||||
|
||||
toContain: function(selector) {
|
||||
return this.actual.find(selector).size() > 0;
|
||||
},
|
||||
|
||||
toBeDisabled: function(selector){
|
||||
return this.actual.is(':disabled');
|
||||
},
|
||||
|
||||
// tests the existence of a specific event binding
|
||||
toHandle: function(eventName) {
|
||||
var events = this.actual.data("events");
|
||||
return events && events[eventName].length > 0;
|
||||
},
|
||||
|
||||
// tests the existence of a specific event binding + handler
|
||||
toHandleWith: function(eventName, eventHandler) {
|
||||
var stack = this.actual.data("events")[eventName];
|
||||
var i;
|
||||
for (i = 0; i < stack.length; i++) {
|
||||
if (stack[i].handler == eventHandler) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var hasProperty = function(actualValue, expectedValue) {
|
||||
if (expectedValue === undefined) {
|
||||
return actualValue !== undefined;
|
||||
}
|
||||
return actualValue == expectedValue;
|
||||
};
|
||||
|
||||
var bindMatcher = function(methodName) {
|
||||
var builtInMatcher = jasmine.Matchers.prototype[methodName];
|
||||
|
||||
jasmine.JQuery.matchersClass[methodName] = function() {
|
||||
if (this.actual instanceof jQuery) {
|
||||
var result = jQueryMatchers[methodName].apply(this, arguments);
|
||||
this.actual = jasmine.JQuery.elementToString(this.actual);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (builtInMatcher) {
|
||||
return builtInMatcher.apply(this, arguments);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
for(var methodName in jQueryMatchers) {
|
||||
bindMatcher(methodName);
|
||||
}
|
||||
})();
|
||||
|
||||
beforeEach(function() {
|
||||
this.addMatchers(jasmine.JQuery.matchersClass);
|
||||
this.addMatchers({
|
||||
toHaveBeenTriggeredOn: function(selector) {
|
||||
this.message = function() {
|
||||
return [
|
||||
"Expected event " + this.actual + " to have been triggered on" + selector,
|
||||
"Expected event " + this.actual + " not to have been triggered on" + selector
|
||||
];
|
||||
};
|
||||
return jasmine.JQuery.events.wasTriggered(selector, this.actual);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.JQuery.events.cleanUp();
|
||||
});
|
||||
|
6
vendor/stringscore.js
vendored
6
vendor/stringscore.js
vendored
@ -1,3 +1,5 @@
|
||||
// MODIFIED BY NS/CJ - Don't extend the prototype of String
|
||||
|
||||
/*!
|
||||
* string_score.js: String Scoring Algorithm 0.1.9
|
||||
*
|
||||
@ -16,10 +18,10 @@
|
||||
* 'Hello World'.score('he'); //=> 0.5931818181818181
|
||||
* 'Hello World'.score('Hello'); //=> 0.7318181818181818
|
||||
*/
|
||||
String.prototype.score = function(abbreviation, fuzziness) {
|
||||
|
||||
module.exports = function(string, abbreviation, fuzziness) {
|
||||
var total_character_score = 0,
|
||||
abbreviation_length = abbreviation.length,
|
||||
string = this,
|
||||
string_length = string.length,
|
||||
start_of_string_bonus,
|
||||
abbreviation_score,
|
||||
|
Loading…
Reference in New Issue
Block a user