` whose class name is tied to a
19 | // property of a JS object.
20 | //
21 | // Template:
22 | //
23 | //
30 | //
31 | // Data:
32 | //
33 | // {
34 | // "post": {
35 | // "title": "My Post",
36 | // "entry": "My Entry",
37 | // "author": {
38 | // "name": "@aq"
39 | // }
40 | // }
41 | // }
42 | //
43 | // Result:
44 | //
45 | //
52 | //
53 | // Templates can be much more complex, and more deeply nested.
54 | // More examples can be found in `test/fixtures/meld/`
55 | //
56 | // If you don't think the lookup by classes is semantic for you, you can easily
57 | // switch the method of lookup by defining a selector function in the options
58 | //
59 | // For example:
60 | //
61 | // meld($('.post'), post_data, {
62 | // selector: function(k) {
63 | // return '[data-key=' + k + ']';
64 | // }
65 | // });
66 | //
67 | // Would look for template nodes like `
`
68 | //
69 | Sammy.Meld = function(app, method_alias) {
70 | var default_options = {
71 | selector: function(k) { return '.' + k; },
72 | remove_false: true
73 | };
74 |
75 | var meld = function(template, data, options) {
76 | var $template = $(template);
77 |
78 | options = $.extend(default_options, options || {});
79 |
80 | if (typeof data === 'string') {
81 | $template.html(data);
82 | } else {
83 | $.each(data, function(key, value) {
84 | var selector = options.selector(key),
85 | $sub = $template.filter(selector),
86 | $container,
87 | $item,
88 | is_list = false,
89 | subindex = $template.index($sub);
90 |
91 | if ($sub.length === 0) { $sub = $template.find(selector); }
92 | if ($sub.length > 0) {
93 | if ($.isArray(value)) {
94 | $container = $('
');
95 | if ($sub.is('ol, ul')) {
96 | is_list = true;
97 | $item = $sub.children('li:first');
98 | if ($item.length == 0) { $item = $('
'); }
99 | } else if ($sub.children().length == 1) {
100 | is_list = true;
101 | $item = $sub.children(':first').clone();
102 | } else {
103 | $item = $sub.clone();
104 | }
105 | for (var i = 0; i < value.length; i++) {
106 | $container.append(meld($item.clone(), value[i], options));
107 | }
108 | if (is_list) {
109 | $sub.html($container.html());
110 | } else if ($sub[0] == $template[0]) {
111 | $template = $($container.html());
112 | } else if (subindex >= 0) {
113 | var args = [subindex, 1];
114 | args = args.concat($container.children().get());
115 | $template.splice.apply($template, args);
116 | }
117 | } else if (options.remove_false && value === false) {
118 | $template.splice(subindex, 1);
119 | } else if (typeof value === 'object') {
120 | if ($sub.is(':empty')) {
121 | $sub.attr(value, true);
122 | } else {
123 | $sub.html(meld($sub.html(), value, options));
124 | }
125 | } else {
126 | $sub.html(value.toString());
127 | }
128 | } else {
129 | $template.attr(key, value, true);
130 | }
131 | });
132 | }
133 | var dom = $template;
134 | return dom;
135 | };
136 |
137 | // set the default method name/extension
138 | if (!method_alias) method_alias = 'meld';
139 | // create the helper at the method alias
140 | app.helper(method_alias, meld);
141 |
142 | };
143 |
144 | return Sammy.Meld;
145 |
146 | }));
147 |
--------------------------------------------------------------------------------
/lib/plugins/sammy.handlebars.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['jquery', 'sammy', 'handlebars'], factory);
4 | } else {
5 | (window.Sammy = window.Sammy || {}).Handlebars = factory(window.jQuery, window.Sammy);
6 | }
7 | }(function ($, Sammy, Handlebars) {
8 | // version 1.0.0 has no support for AMD but upwards does, this way we support both.
9 | Handlebars = Handlebars || window.Handlebars;
10 |
11 | //
Sammy.Handlebars provides a quick way of using Handlebars templates in your app.
12 | //
13 | // Note: As of Sammy 0.7 Handlebars itself is not included in the source. Please download and
14 | // include handlebars.js before Sammy.Handlebars.
15 | //
16 | // Handlebars.js is an extension to the Mustache templating language created by Chris Wanstrath. Handlebars.js
17 | // and Mustache are both logicless templating languages that keep the view and the code separated like
18 | // we all know they should be.
19 | //
20 | // By default using Sammy.Handlbars in your app adds the
handlebars() method to the EventContext
21 | // prototype. However, just like
Sammy.Template you can change the default name of the method
22 | // by passing a second argument (e.g. you could use the hbr() as the method alias so that all the template
23 | // files could be in the form file.hbr instead of file.handlebars)
24 | //
25 | // ### Example #1
26 | //
27 | // The template (mytemplate.hb):
28 | //
29 | //
{{title}}
30 | //
31 | // Hey, {{name}}! Welcome to Handlebars!
32 | //
33 | // The app:
34 | //
35 | // var app = $.sammy(function() {
36 | // // include the plugin and alias handlebars() to hb()
37 | // this.use('Handlebars', 'hb');
38 | //
39 | // this.get('#/hello/:name', function() {
40 | // // set local vars
41 | // this.title = 'Hello!'
42 | // this.name = this.params.name;
43 | // // render the template and pass it through handlebars
44 | // this.partial('mytemplate.hb');
45 | // });
46 | // });
47 | //
48 | // $(function() {
49 | // app.run()
50 | // });
51 | //
52 | // If I go to #/hello/AQ in the browser, Sammy will render this to the body:
53 | //
54 | // Hello!
55 | //
56 | // Hey, AQ! Welcome to Handlebars!
57 | //
58 | //
59 | // ### Example #2 - Handlebars partials
60 | //
61 | // The template (mytemplate.hb)
62 | //
63 | // Hey, {{name}}! {{>hello_friend}}
64 | //
65 | //
66 | // The partial (mypartial.hb)
67 | //
68 | // Say hello to your friend {{friend}}!
69 | //
70 | // The app:
71 | //
72 | // var app = $.sammy(function() {
73 | // // include the plugin and alias handlebars() to hb()
74 | // this.use('Handlebars', 'hb');
75 | //
76 | // this.get('#/hello/:name/to/:friend', function(context) {
77 | // // fetch handlebars-partial first
78 | // this.load('mypartial.hb')
79 | // .then(function(partial) {
80 | // // set local vars
81 | // context.partials = {hello_friend: partial};
82 | // context.name = context.params.name;
83 | // context.friend = context.params.friend;
84 | //
85 | // // render the template and pass it through handlebars
86 | // context.partial('mytemplate.hb');
87 | // });
88 | // });
89 | // });
90 | //
91 | // $(function() {
92 | // app.run()
93 | // });
94 | //
95 | // If I go to #/hello/AQ/to/dP in the browser, Sammy will render this to the body:
96 | //
97 | // Hey, AQ! Say hello to your friend dP!
98 | //
99 | // Note: You dont have to include the handlebars.js file on top of the plugin as the plugin
100 | // includes the full source.
101 | //
102 | Sammy.Handlebars = function(app, method_alias) {
103 | var handlebars_cache = {};
104 | // *Helper* Uses handlebars.js to parse a template and interpolate and work with the passed data
105 | //
106 | // ### Arguments
107 | //
108 | // * `template` A String template.
109 | // * `data` An Object containing the replacement values for the template.
110 | // data is extended with the EventContext allowing you to call its methods within the template.
111 | //
112 | var handlebars = function(template, data, partials, name) {
113 | // use name for caching
114 | if (typeof name == 'undefined') { name = template; }
115 | var fn = handlebars_cache[name];
116 | if (!fn) {
117 | fn = handlebars_cache[name] = Handlebars.compile(template);
118 | }
119 |
120 | data = $.extend({}, this, data);
121 | partials = $.extend({}, data.partials, partials);
122 |
123 | return fn(data, {"partials":partials});
124 | };
125 |
126 | // set the default method name/extension
127 | if (!method_alias) { method_alias = 'handlebars'; }
128 | app.helper(method_alias, handlebars);
129 | };
130 |
131 | return Sammy.Handlebars;
132 |
133 | }));
134 |
--------------------------------------------------------------------------------
/test/meld_spec.js:
--------------------------------------------------------------------------------
1 | describe('Meld', function() {
2 | var context;
3 |
4 | beforeEach(function() {
5 | var app = new Sammy.Application(function() {
6 | this.raise_errors = false;
7 | this.element_selector = '#main';
8 | this.use(Sammy.Meld);
9 | this.get('#/', function() {});
10 | });
11 |
12 | context = new app.context_prototype(app, 'get', '#/test/:test', {test: 'hooray'});
13 | });
14 |
15 | it('does simple interpolation', function() {
16 | var template = "
";
17 | data = {'title': 'TEST'},
18 | expected = "TEST
";
19 |
20 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
21 | });
22 |
23 | it('interpolates one level deep', function() {
24 | var template = "";
25 | data = {'post': {'title': 'TEST'}},
26 | expected = "";
27 |
28 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
29 | });
30 |
31 | it('interpolates multiple keys', function() {
32 | var template = "";
33 | data = {'post': 'my post', 'title': 'TEST'},
34 | expected = "my post
TEST
";
35 |
36 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
37 | });
38 |
39 | it('interpolates multiple nested keys', function() {
40 | var template = "";
41 | data = {'post': {'title': 'TEST', 'author': 'AQ'}},
42 | expected = "";
43 |
44 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
45 | });
46 |
47 | it('multiplies an array of tags', function() {
48 | var template = "
";
49 | data = {'post': {'tags': ['one', 'two']}},
50 | expected = "onetwo
";
51 |
52 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
53 | });
54 |
55 | it('multiplies an array of objects', function() {
56 | var template = "";
57 | data = {'post': {'authors': [{'name': 'AQ', 'twitter': 'aq'}, {'name':'Mike', 'twitter':'mrb_bk'}]}},
58 | expected = "";
59 |
60 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
61 | });
62 |
63 | it('multiplies an array on a list item', function() {
64 | var template = "";
65 | data = {'post': {'tags': ['one', 'two']}},
66 | expected = "";
67 |
68 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
69 | });
70 |
71 | it('multiplies an array on a list item using the example list item', function() {
72 | var template = "";
73 | data = {'post': {'tags': ['one', 'two']}},
74 | expected = "";
75 |
76 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
77 | });
78 |
79 | it('replaces attributes of elements as a fallback to class lookup', function() {
80 | var template = "",
81 | data = {'post': {'name': {'href': 'http://www.google.com', 'text': 'Link'}}},
82 | expected = "";
83 |
84 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
85 | });
86 |
87 | it('removes nodes if value is false', function() {
88 | var template = "Active",
89 | data = {'post': {'name': "My Name", 'active': false}},
90 | expected = "My Name
";
91 |
92 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected);
93 | });
94 |
95 | it('allows the setting of a selector function', function() {
96 | var template = "",
97 | data = {'post': {'name': "My Name"}},
98 | expected = "My Name
";
99 |
100 | expect(context.meld(template, data, {selector: function(k) {
101 | return "[rel='"+ k + "']";
102 | }})).to.have.sameHTMLAs(expected);
103 | });
104 |
105 | it('renders templates correctly', function(done) {
106 | var templates = 3;
107 |
108 | var getAndAssertTemplate = function(i) {
109 | var template, json, result;
110 | $.get('fixtures/meld/' + i + '.meld', function(t) {
111 | template = t;
112 | $.getJSON('fixtures/meld/' + i + '.json', function(j) {
113 | json = j;
114 | $.get('fixtures/meld/' + i + '.html', function(r) {
115 | expect(context.meld(template, json)).to.have.sameHTMLAs(r);
116 | if (i == templates) {
117 | done();
118 | } else {
119 | getAndAssertTemplate(i + 1);
120 | }
121 | });
122 | });
123 | });
124 | }
125 |
126 | getAndAssertTemplate(1);
127 | });
128 | });
129 |
--------------------------------------------------------------------------------
/lib/plugins/sammy.template.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['jquery', 'sammy'], factory);
4 | } else {
5 | (window.Sammy = window.Sammy || {}).Template = factory(window.jQuery, window.Sammy);
6 | }
7 | }(function ($, Sammy) {
8 |
9 | // Simple JavaScript Templating
10 | // John Resig - http://ejohn.org/ - MIT Licensed
11 | // adapted from: http://ejohn.org/blog/javascript-micro-templating/
12 | // originally $.srender by Greg Borenstein http://ideasfordozens.com in Feb 2009
13 | // modified for Sammy by Aaron Quint for caching templates by name
14 | var srender_cache = {};
15 | var srender = function(name, template, data, options) {
16 | var fn, escaped_string;
17 | // target is an optional element; if provided, the result will be inserted into it
18 | // otherwise the result will simply be returned to the caller
19 | if (srender_cache[name]) {
20 | fn = srender_cache[name];
21 | } else {
22 | if (typeof template == 'undefined') {
23 | // was a cache check, return false
24 | return false;
25 | }
26 | // If options escape_html is false, dont escape the contents by default
27 | if (options && options.escape_html === false) {
28 | escaped_string = "\",$1,\"";
29 | } else {
30 | escaped_string = "\",h($1),\"";
31 | }
32 | // Generate a reusable function that will serve as a template
33 | // generator (and which will be cached).
34 | fn = srender_cache[name] = new Function("obj",
35 | "var ___$$$___=[],print=function(){___$$$___.push.apply(___$$$___,arguments);};" +
36 |
37 | // Introduce the data as local variables using with(){}
38 | "with(obj){___$$$___.push(\"" +
39 |
40 | // Convert the template into pure JavaScript
41 | String(template)
42 | .replace(/[\r\t\n]/g, " ")
43 | .replace(/\"/g, '\\"')
44 | .split("<%").join("\t")
45 | .replace(/((^|%>)[^\t]*)/g, "$1\r")
46 | .replace(/\t=(.*?)%>/g, escaped_string)
47 | .replace(/\t!(.*?)%>/g, "\",$1,\"")
48 | .split("\t").join("\");")
49 | .split("%>").join("___$$$___.push(\"")
50 | .split("\r").join("")
51 | + "\");}return ___$$$___.join('');");
52 | }
53 |
54 | if (typeof data != 'undefined') {
55 | return fn(data);
56 | } else {
57 | return fn;
58 | }
59 | };
60 |
61 | // `Sammy.Template` is a simple plugin that provides a way to create
62 | // and render client side templates. The rendering code is based on John Resig's
63 | // quick templates and Greg Borenstien's srender plugin.
64 | // This is also a great template/boilerplate for Sammy plugins.
65 | //
66 | // Templates use `<% %>` tags to denote embedded javascript.
67 | //
68 | // ### Examples
69 | //
70 | // Here is an example template (user.template):
71 | //
72 | // // user.template
73 | //
74 | //
<%= user.name %>
75 | // <% if (user.photo_url) { %>
76 | //
77 | // <% } %>
78 | //
79 | //
80 | // Given that is a publicly accesible file, you would render it like:
81 | //
82 | // // app.js
83 | // $.sammy(function() {
84 | // // include the plugin
85 | // this.use('Template');
86 | //
87 | // this.get('#/', function() {
88 | // // the template is rendered in the current context.
89 | // this.user = {name: 'Aaron Quint'};
90 | // // partial calls template() because of the file extension
91 | // this.partial('user.template');
92 | // })
93 | // });
94 | //
95 | // You can also pass a second argument to use() that will alias the template
96 | // method and therefore allow you to use a different extension for template files
97 | // in partial()
98 | //
99 | // // alias to 'tpl'
100 | // this.use(Sammy.Template, 'tpl');
101 | //
102 | // // now .tpl files will be run through srender
103 | // this.get('#/', function() {
104 | // this.partial('myfile.tpl');
105 | // });
106 | //
107 | // By default, the data passed into the tempalate is passed automatically passed through
108 | // Sammy's `escapeHTML` method in order to prevent possible XSS attacks. This is
109 | // a problem though if you're using something like `Sammy.Form` which renders HTML
110 | // within the templates. You can get around this in two ways. One, you can use the
111 | // `<%! %>` instead of `<%= %>`. Two, you can pass the `escape_html = false` option
112 | // when interpolating, i.e:
113 | //
114 | // this.get('#/', function() {
115 | // this.template('myform.tpl', {form: ""}, {escape_html: false});
116 | // });
117 | //
118 | Sammy.Template = function(app, method_alias) {
119 |
120 | // *Helper:* Uses simple templating to parse ERB like templates.
121 | //
122 | // ### Arguments
123 | //
124 | // * `template` A String template. '<% %>' tags are evaluated as Javascript and replaced with the elements in data.
125 | // * `data` An Object containing the replacement values for the template.
126 | // data is extended with the
EventContext allowing you to call its methods within the template.
127 | // * `name` An optional String name to cache the template.
128 | //
129 | var template = function(template, data, name, options) {
130 | // use name for caching
131 | if (typeof name == 'undefined') { name = template; }
132 | if (typeof options == 'undefined' && typeof name == 'object') {
133 | options = name; name = template;
134 | }
135 | return srender(name, template, $.extend({}, this, data), options);
136 | };
137 |
138 | // set the default method name/extension
139 | if (!method_alias) { method_alias = 'template'; }
140 | // create the helper at the method alias
141 | app.helper(method_alias, template);
142 |
143 | };
144 |
145 | return Sammy.Template;
146 |
147 | }));
148 |
--------------------------------------------------------------------------------
/lib/plugins/sammy.oauth2.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['jquery', 'sammy'], factory);
4 | } else {
5 | (window.Sammy = window.Sammy || {}).OAuth2 = factory(window.jQuery, window.Sammy);
6 | }
7 | }(function ($, Sammy) {
8 |
9 | // Sammy.OAuth2 is a plugin for using OAuth 2.0 to authenticate users and
10 | // access your application's API. Requires Sammy.Session.
11 | //
12 | // Triggers the following events:
13 | //
14 | // * `oauth.connected` - Access token set and ready to use. Triggered when new
15 | // access token acquired, of when application starts and already has access
16 | // token.
17 | // * `oauth.disconnected` - Access token reset. Triggered by
18 | // loseAccessToken().
19 | // * `oauth.denied` - Authorization attempt rejected.
20 | //
21 | // ### Example
22 | //
23 | // this.use('Storage');
24 | // this.use('OAuth2');
25 | // this.oauthorize = "/oauth/authorize";
26 | //
27 | // // The quick & easy way
28 | // this.requireOAuth();
29 | // // Specific path
30 | // this.requireOAuth("/private");
31 | // // Filter you can apply to specific URLs
32 | // this.before(function(context) { return context.requireOAuth(); })
33 | // // Apply to specific request
34 | // this.get("/private", function(context) {
35 | // this.requireOAuth(function() {
36 | // // Do something
37 | // });
38 | // });
39 | //
40 | // // Sign in/sign out.
41 | // this.bind("oauth.connected", function() { $("#signin").hide() });
42 | // this.bind("oauth.disconnected", function() { $("#signin").show() });
43 | //
44 | // // Handle access denied and other errors
45 | // this.bind("oauth.denied", function(evt, error) {
46 | // this.partial("admin/views/no_access.tmpl", { error: error.message });
47 | // });
48 | //
49 | // // Sign out.
50 | // this.get("#/signout", function(context) {
51 | // context.loseAccessToken();
52 | // context.redirect("#/");
53 | // });
54 | //
55 | Sammy.OAuth2 = function(app) {
56 | app.use('JSON');
57 | this.authorize = "/oauth/authorize";
58 |
59 | // Use this on request that require OAuth token. You can use this in a
60 | // filter: it will redirect and return false if the access token is missing.
61 | // You can use it in a route, it will redirect to get the access token, or
62 | // call the callback function if it has an access token.
63 | this.helper("requireOAuth", function(cb) {
64 | if (this.app.getAccessToken()) {
65 | if (cb) {
66 | cb.apply(this);
67 | }
68 | } else {
69 | this.redirect(this.app.authorize + "?state=" + escape(this.path));
70 | return false;
71 | }
72 | });
73 |
74 | // Use this to sign out.
75 | this.helper("loseAccessToken", function() {
76 | this.app.loseAccessToken();
77 | });
78 |
79 | // Use this in your application to require an OAuth access token on all, or
80 | // the specified paths. It sets up a before filter on the specified paths.
81 | this.requireOAuth = function(options) {
82 | this.before(options || {}, function(context) {
83 | return context.requireOAuth();
84 | });
85 | }
86 |
87 | // Returns the access token. Uses Sammy.Session to store the token.
88 | this.getAccessToken = function() {
89 | return this.session("oauth.token");
90 | }
91 | // Stores the access token in the session.
92 | this.setAccessToken = function(token) {
93 | this.session("oauth.token", token);
94 | this.trigger("oauth.connected");
95 | }
96 | // Lose access token: use this to sign out.
97 | this.loseAccessToken = function() {
98 | this.session("oauth.token", null);
99 | this.trigger("oauth.disconnected");
100 | }
101 |
102 | // Add OAuth 2.0 access token to all XHR requests.
103 | $(document).ajaxSend(function(evt, xhr) {
104 | var token = app.getAccessToken();
105 | if (token) {
106 | xhr.setRequestHeader("Authorization", "OAuth " + token);
107 | }
108 | });
109 |
110 | // Converts query string parameters in fragment identifier to object.
111 | function parseParams(path) {
112 | var hash = path.match(/#(.*)$/)[1];
113 | var pairs = hash.split("&"), params = {};
114 | var i, len = pairs.length;
115 |
116 | for (i = 0; i < len; i += 1) {
117 | var splat = pairs[i].split("=");
118 | params[splat[0]] = splat[1].replace(/\+/g, " ");
119 | }
120 | return params;
121 | }
122 |
123 | var start_url;
124 | // Capture the application's start URL, we'll need that later on for
125 | // redirection.
126 | this.bind("run", function(evt, params) {
127 | start_url = params.start_url || "#";
128 | if (this.app.getAccessToken()) {
129 | this.trigger("oauth.connected");
130 | }
131 | });
132 |
133 | // Intercept OAuth authorization response with access token, stores it and
134 | // redirects to original URL, or application root.
135 | this.before(/#(access_token=|[^\\].*\&access_token=)/, function(context) {
136 | var params = parseParams(context.path);
137 | this.app.setAccessToken(params.access_token);
138 | // When the filter redirected the original request, it passed the original
139 | // request's URL in the state parameter, which we get back after
140 | // authorization.
141 | context.redirect(params.state.length === 0 ? this.app.start_url : unescape(params.state));
142 | return false;
143 | }).get(/#(access_token=|[^\\].*\&access_token=)/, function(context) { });
144 |
145 | // Intercept OAuth authorization response with error (typically access
146 | // denied).
147 | this.before(/#(error=|[^\\].*\&error=)/, function(context) {
148 | var params = parseParams(context.path);
149 | var message = params.error_description || "Access denined";
150 | context.trigger("oauth.denied", { code: params.error, message: message });
151 | return false;
152 | }).get(/#(error=|[^\\].*\&error=)/, function(context) { });
153 |
154 | }
155 |
156 | return Sammy.OAuth2;
157 |
158 | }));
159 |
--------------------------------------------------------------------------------
/test/flash_spec.js:
--------------------------------------------------------------------------------
1 | describe('Flash', function() {
2 | var app, nowApp, context, output,
3 | createAppsWithFlash, unloadAppsWithFlash;
4 |
5 | createAppsWithFlash = function() {
6 | app = Sammy(function() {
7 | this.use(Sammy.Flash);
8 | this.element_selector = '#main';
9 |
10 | this.get('#/', function() {
11 | this.flash('welcome info', 'Welcome!');
12 | });
13 | this.post('#/test', function() {
14 | this.flash('info', "Successfully POSTed nested params");
15 | this.redirect('#/');
16 | });
17 | });
18 |
19 | nowApp = Sammy(function() {
20 | this.use(Sammy.Flash);
21 | this.element_selector = '#main2';
22 |
23 | this.get('#/', function() {
24 | this.flashNow('info', '您好');
25 | });
26 | this.post('#/test', function() {
27 | this.flashNow('warn', 'Uh-oh?');
28 | this.redirect('#/doNothing');
29 | });
30 | this.get('#/doNothing', function() {});
31 | });
32 | };
33 |
34 | unloadApps = function() {
35 | app.unload();
36 | nowApp.unload();
37 | window.location.href = '#/';
38 | };
39 |
40 | describe('app.flash', function() {
41 | beforeEach(createAppsWithFlash);
42 | afterEach(unloadApps);
43 |
44 | it('exists', function() {
45 | expect(app.flash).to.be.an(Object);
46 | });
47 |
48 | it('retains entries after a non-redirect', function() {
49 | app.run('#/');
50 | expect(app.flash['welcome info']).to.eql('Welcome!');
51 | });
52 |
53 | it('retains entries after a redirect', function() {
54 | $('#main').html('
');
57 |
58 | app.run('#/');
59 | $('#test_form').submit();
60 | expect(app.flash['info']).to.eql("Successfully POSTed nested params");
61 | });
62 |
63 | it('loses all entries after being rendered', function() {
64 | app.run('#/');
65 | app.flash.toHTML();
66 | expect(app.flash['welcome info']).to.be(undefined);
67 | });
68 |
69 | it('retains entries after a redirect in another app', function() {
70 | $('#main2').html('
');
73 |
74 | app.run('#/');
75 | nowApp.run('#/');
76 | $('#test_form').submit();
77 | expect(app.flash['welcome info']).to.eql('Welcome!');
78 | });
79 | });
80 |
81 | describe('app.flash.now', function() {
82 | beforeEach(createAppsWithFlash);
83 | afterEach(unloadApps);
84 |
85 | it('exists', function() {
86 | expect(nowApp.flash).to.be.an(Object);
87 | });
88 |
89 | it('retains entries after a non-redirect', function() {
90 | nowApp.run('#/');
91 | window.location.hash = '#/';
92 | expect(nowApp.flash.now.info).to.eql('您好');
93 | });
94 |
95 | it('loses all entries after a redirect', function() {
96 | $('#main').html('');
97 | $('#main2').html('
');
100 |
101 | nowApp.run('#/');
102 | $('#test_form').submit();
103 | expect(nowApp.flash.now.warn).to.be(undefined);
104 | });
105 |
106 | it('loses all entries after being rendered', function() {
107 | nowApp.run('#/');
108 | nowApp.flash.toHTML();
109 | expect(nowApp.flash.now.info).to.be(undefined);
110 | });
111 |
112 | it('retains entries after a redirect in another app', function() {
113 | $('#main2').html('');
114 | $('#main').html('
');
117 |
118 | app.run('#/');
119 | nowApp.run('#/');
120 | $('#test_form').submit();
121 | expect(nowApp.flash.now.info).to.eql('您好');
122 | });
123 | });
124 |
125 | describe('#flash()', function() {
126 | beforeEach(function() {
127 | createAppsWithFlash();
128 | context = new app.context_prototype(app, 'get', '#/', {});
129 | });
130 |
131 | it('returns the Flash object when passed no arguments', function() {
132 | expect(context.flash()).to.eql(app.flash);
133 | });
134 |
135 | it('returns the value of the given key when passed one argument', function() {
136 | app.flash.foo = 'bar';
137 | expect(context.flash('foo')).to.eql('bar');
138 | });
139 |
140 | it('sets a flash value when passed two arguments', function() {
141 | context.flash('foo2', 'bar2');
142 | expect(context.flash('foo2')).to.eql('bar2');
143 | });
144 | });
145 |
146 | describe('#flashNow()', function() {
147 | beforeEach(function() {
148 | createAppsWithFlash();
149 | context = new app.context_prototype(app, 'get', '#/', {});
150 | });
151 |
152 | it('returns the Flash-Now object when passed no arguments', function() {
153 | expect(context.flashNow()).to.eql(app.flash.now);
154 | });
155 |
156 | it('returns the value of the given key when passed one argument', function() {
157 | app.flash.now.foo = 'bar';
158 | expect(context.flashNow('foo')).to.eql('bar');
159 | });
160 |
161 | it('sets a flash value when passed two arguments', function() {
162 | context.flashNow('foo2', 'bar2');
163 | expect(context.flashNow('foo2')).to.eql('bar2');
164 | });
165 | });
166 |
167 | describe('#app.flash.toHTML()', function() {
168 | beforeEach(function() {
169 | createAppsWithFlash();
170 | app.flash.clear();
171 | nowApp.flash.clear();
172 |
173 | app.flash.error = 'Boom!';
174 | app.flash.warn = 'Beep!';
175 | app.flash.now.info = 'Info!';
176 | nowApp.flash.debug = 'Debug!';
177 |
178 | output = $('
')
179 | .append(app.flash.toHTML())
180 | .appendTo($('body'));
181 | });
182 |
183 | afterEach(function() {
184 | output.remove();
185 | });
186 |
187 | it('renders a ul.flash', function() {
188 | expect($('ul.flash', this.output)).to.have.length(1);
189 | });
190 |
191 | it('includes entries from both flash and flash.now, with keys as classes', function() {
192 | expect($('ul.flash li.error', this.output).text()).to.eql('Boom!');
193 | expect($('ul.flash li.warn', this.output).text()).to.eql('Beep!');
194 | expect($('ul.flash li.info', this.output).text()).to.eql('Info!');
195 | });
196 |
197 | it("does not include entries from another app's flash", function() {
198 | expect($('.debug', output)).to.have.length(0);
199 | });
200 | });
201 | });
202 |
--------------------------------------------------------------------------------