Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Soy data primitives.
|
||||
*
|
||||
* The goal is to encompass data types used by Soy, especially to mark content
|
||||
* as known to be "safe".
|
||||
*
|
||||
* @author gboyer@google.com (Garrett Boyer)
|
||||
*/
|
||||
|
||||
goog.provide('goog.soy.data.SanitizedContent');
|
||||
goog.provide('goog.soy.data.SanitizedContentKind');
|
||||
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.html.uncheckedconversions');
|
||||
goog.require('goog.string.Const');
|
||||
|
||||
|
||||
/**
|
||||
* A type of textual content.
|
||||
*
|
||||
* This is an enum of type Object so that these values are unforgeable.
|
||||
*
|
||||
* @enum {!Object}
|
||||
*/
|
||||
goog.soy.data.SanitizedContentKind = {
|
||||
|
||||
/**
|
||||
* A snippet of HTML that does not start or end inside a tag, comment, entity,
|
||||
* or DOCTYPE; and that does not contain any executable code
|
||||
* (JS, {@code <object>}s, etc.) from a different trust domain.
|
||||
*/
|
||||
HTML: goog.DEBUG ? {sanitizedContentKindHtml: true} : {},
|
||||
|
||||
/**
|
||||
* Executable Javascript code or expression, safe for insertion in a
|
||||
* script-tag or event handler context, known to be free of any
|
||||
* attacker-controlled scripts. This can either be side-effect-free
|
||||
* Javascript (such as JSON) or Javascript that's entirely under Google's
|
||||
* control.
|
||||
*/
|
||||
JS: goog.DEBUG ? {sanitizedContentJsChars: true} : {},
|
||||
|
||||
/** A properly encoded portion of a URI. */
|
||||
URI: goog.DEBUG ? {sanitizedContentUri: true} : {},
|
||||
|
||||
/**
|
||||
* Repeated attribute names and values. For example,
|
||||
* {@code dir="ltr" foo="bar" onclick="trustedFunction()" checked}.
|
||||
*/
|
||||
ATTRIBUTES: goog.DEBUG ? {sanitizedContentHtmlAttribute: true} : {},
|
||||
|
||||
// TODO: Consider separating rules, declarations, and values into
|
||||
// separate types, but for simplicity, we'll treat explicitly blessed
|
||||
// SanitizedContent as allowed in all of these contexts.
|
||||
/**
|
||||
* A CSS3 declaration, property, value or group of semicolon separated
|
||||
* declarations.
|
||||
*/
|
||||
CSS: goog.DEBUG ? {sanitizedContentCss: true} : {},
|
||||
|
||||
/**
|
||||
* Unsanitized plain-text content.
|
||||
*
|
||||
* This is effectively the "null" entry of this enum, and is sometimes used
|
||||
* to explicitly mark content that should never be used unescaped. Since any
|
||||
* string is safe to use as text, being of ContentKind.TEXT makes no
|
||||
* guarantees about its safety in any other context such as HTML.
|
||||
*/
|
||||
TEXT: goog.DEBUG ? {sanitizedContentKindText: true} : {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A string-like object that carries a content-type and a content direction.
|
||||
*
|
||||
* IMPORTANT! Do not create these directly, nor instantiate the subclasses.
|
||||
* Instead, use a trusted, centrally reviewed library as endorsed by your team
|
||||
* to generate these objects. Otherwise, you risk accidentally creating
|
||||
* SanitizedContent that is attacker-controlled and gets evaluated unescaped in
|
||||
* templates.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.soy.data.SanitizedContent = function() {
|
||||
throw Error('Do not instantiate directly');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The context in which this content is safe from XSS attacks.
|
||||
* @type {goog.soy.data.SanitizedContentKind}
|
||||
*/
|
||||
goog.soy.data.SanitizedContent.prototype.contentKind;
|
||||
|
||||
|
||||
/**
|
||||
* The content's direction; null if unknown and thus to be estimated when
|
||||
* necessary.
|
||||
* @type {?goog.i18n.bidi.Dir}
|
||||
*/
|
||||
goog.soy.data.SanitizedContent.prototype.contentDir = null;
|
||||
|
||||
|
||||
/**
|
||||
* The already-safe content.
|
||||
* @protected {string}
|
||||
*/
|
||||
goog.soy.data.SanitizedContent.prototype.content;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the already-safe content.
|
||||
* @return {string}
|
||||
*/
|
||||
goog.soy.data.SanitizedContent.prototype.getContent = function() {
|
||||
return this.content;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.soy.data.SanitizedContent.prototype.toString = function() {
|
||||
return this.content;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts sanitized content of kind TEXT or HTML into SafeHtml. HTML content
|
||||
* is converted without modification, while text content is HTML-escaped.
|
||||
* @return {!goog.html.SafeHtml}
|
||||
* @throws {Error} when the content kind is not TEXT or HTML.
|
||||
*/
|
||||
goog.soy.data.SanitizedContent.prototype.toSafeHtml = function() {
|
||||
if (this.contentKind === goog.soy.data.SanitizedContentKind.TEXT) {
|
||||
return goog.html.SafeHtml.htmlEscape(this.toString());
|
||||
}
|
||||
if (this.contentKind !== goog.soy.data.SanitizedContentKind.HTML) {
|
||||
throw Error('Sanitized content was not of kind TEXT or HTML.');
|
||||
}
|
||||
return goog.html.uncheckedconversions.
|
||||
safeHtmlFromStringKnownToSatisfyTypeContract(
|
||||
goog.string.Const.from(
|
||||
'Soy SanitizedContent of kind HTML produces ' +
|
||||
'SafeHtml-contract-compliant value.'),
|
||||
this.toString(), this.contentDir);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2014 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.soy.data
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.soy.dataTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.soy.dataTest');
|
||||
goog.setTestOnly('goog.soy.dataTest');
|
||||
|
||||
goog.require('goog.html.SafeHtml');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('goog.soy.testHelper');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
function testToSafeHtml() {
|
||||
var html;
|
||||
|
||||
html = example.unsanitizedTextTemplate().toSafeHtml();
|
||||
assertEquals('I <3 Puppies & Kittens',
|
||||
goog.html.SafeHtml.unwrap(html));
|
||||
|
||||
html = example.sanitizedHtmlTemplate().toSafeHtml();
|
||||
assertEquals('Hello <b>World</b>', goog.html.SafeHtml.unwrap(html));
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a soy renderer that allows registration of
|
||||
* injected data ("globals") that will be passed into the rendered
|
||||
* templates.
|
||||
*
|
||||
* There is also an interface {@link goog.soy.InjectedDataSupplier} that
|
||||
* user should implement to provide the injected data for a specific
|
||||
* application. The injected data format is a JavaScript object:
|
||||
* <pre>
|
||||
* {'dataKey': 'value', 'otherDataKey': 'otherValue'}
|
||||
* </pre>
|
||||
*
|
||||
* To use injected data, you need to enable the soy-to-js compiler
|
||||
* option {@code --isUsingIjData}. The injected data can then be
|
||||
* referred to in any soy templates as part of a magic "ij"
|
||||
* parameter. For example, {@code $ij.dataKey} will evaluate to
|
||||
* 'value' with the above injected data.
|
||||
*
|
||||
* @author henrywong@google.com (Henry Wong)
|
||||
* @author chrishenry@google.com (Chris Henry)
|
||||
*/
|
||||
|
||||
goog.provide('goog.soy.InjectedDataSupplier');
|
||||
goog.provide('goog.soy.Renderer');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.soy');
|
||||
goog.require('goog.soy.data.SanitizedContent');
|
||||
goog.require('goog.soy.data.SanitizedContentKind');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new soy renderer. Note that the renderer will only be
|
||||
* guaranteed to work correctly within the document scope provided in
|
||||
* the DOM helper.
|
||||
*
|
||||
* @param {goog.soy.InjectedDataSupplier=} opt_injectedDataSupplier A supplier
|
||||
* that provides an injected data.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper;
|
||||
* defaults to that provided by {@code goog.dom.getDomHelper()}.
|
||||
* @constructor
|
||||
*/
|
||||
goog.soy.Renderer = function(opt_injectedDataSupplier, opt_domHelper) {
|
||||
/**
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.dom_ = opt_domHelper || goog.dom.getDomHelper();
|
||||
|
||||
/**
|
||||
* @type {goog.soy.InjectedDataSupplier}
|
||||
* @private
|
||||
*/
|
||||
this.supplier_ = opt_injectedDataSupplier || null;
|
||||
|
||||
/**
|
||||
* Map from template name to the data used to render that template.
|
||||
* @type {!goog.soy.Renderer.SavedTemplateRender}
|
||||
* @private
|
||||
*/
|
||||
this.savedTemplateRenders_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Array<{template: string, data: Object, ijData: Object}>}
|
||||
*/
|
||||
goog.soy.Renderer.SavedTemplateRender;
|
||||
|
||||
|
||||
/**
|
||||
* Renders a Soy template into a single node or a document fragment.
|
||||
* Delegates to {@code goog.soy.renderAsFragment}.
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):*} template
|
||||
* The Soy template defining the element's content.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @return {!Node} The resulting node or document fragment.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.Renderer.prototype.renderAsFragment = function(template,
|
||||
opt_templateData) {
|
||||
this.saveTemplateRender_(template, opt_templateData);
|
||||
var node = goog.soy.renderAsFragment(template, opt_templateData,
|
||||
this.getInjectedData_(), this.dom_);
|
||||
this.handleRender(node);
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a Soy template into a single node. If the rendered HTML
|
||||
* string represents a single node, then that node is returned.
|
||||
* Otherwise, a DIV element is returned containing the rendered nodes.
|
||||
* Delegates to {@code goog.soy.renderAsElement}.
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):*} template
|
||||
* The Soy template defining the element's content.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @return {!Element} Rendered template contents, wrapped in a parent DIV
|
||||
* element if necessary.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.Renderer.prototype.renderAsElement = function(template,
|
||||
opt_templateData) {
|
||||
this.saveTemplateRender_(template, opt_templateData);
|
||||
var element = goog.soy.renderAsElement(template, opt_templateData,
|
||||
this.getInjectedData_(), this.dom_);
|
||||
this.handleRender(element);
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a Soy template and then set the output string as the
|
||||
* innerHTML of the given element. Delegates to {@code goog.soy.renderElement}.
|
||||
*
|
||||
* @param {Element} element The element whose content we are rendering.
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):*} template
|
||||
* The Soy template defining the element's content.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.Renderer.prototype.renderElement = function(element, template,
|
||||
opt_templateData) {
|
||||
this.saveTemplateRender_(template, opt_templateData);
|
||||
goog.soy.renderElement(
|
||||
element, template, opt_templateData, this.getInjectedData_());
|
||||
this.handleRender(element);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a Soy template and returns the output string.
|
||||
* If the template is strict, it must be of kind HTML. To render strict
|
||||
* templates of other kinds, use {@code renderText} (for {@code kind="text"}) or
|
||||
* {@code renderStrict}.
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):*} template
|
||||
* The Soy template to render.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @return {string} The return value of rendering the template directly.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.Renderer.prototype.render = function(template, opt_templateData) {
|
||||
var result = template(
|
||||
opt_templateData || {}, undefined, this.getInjectedData_());
|
||||
goog.asserts.assert(!(result instanceof goog.soy.data.SanitizedContent) ||
|
||||
result.contentKind === goog.soy.data.SanitizedContentKind.HTML,
|
||||
'render was called with a strict template of kind other than "html"' +
|
||||
' (consider using renderText or renderStrict)');
|
||||
this.saveTemplateRender_(template, opt_templateData);
|
||||
this.handleRender();
|
||||
return String(result);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a strict Soy template of kind="text" and returns the output string.
|
||||
* It is an error to use renderText on non-strict templates, or strict templates
|
||||
* of kinds other than "text".
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):
|
||||
* goog.soy.data.SanitizedContent} template The Soy template to render.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @return {string} The return value of rendering the template directly.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.Renderer.prototype.renderText = function(template, opt_templateData) {
|
||||
var result = template(
|
||||
opt_templateData || {}, undefined, this.getInjectedData_());
|
||||
goog.asserts.assertInstanceof(result, goog.soy.data.SanitizedContent,
|
||||
'renderText cannot be called on a non-strict soy template');
|
||||
goog.asserts.assert(
|
||||
result.contentKind === goog.soy.data.SanitizedContentKind.TEXT,
|
||||
'renderText was called with a template of kind other than "text"');
|
||||
this.saveTemplateRender_(template, opt_templateData);
|
||||
this.handleRender();
|
||||
return String(result);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a strict Soy template and returns the output SanitizedContent object.
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):RETURN_TYPE}
|
||||
* template The Soy template to render.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @param {goog.soy.data.SanitizedContentKind=} opt_kind The output kind to
|
||||
* assert. If null, the template must be of kind="html" (i.e., opt_kind
|
||||
* defaults to goog.soy.data.SanitizedContentKind.HTML).
|
||||
* @return {RETURN_TYPE} The SanitizedContent object. This return type is
|
||||
* generic based on the return type of the template, such as
|
||||
* soy.SanitizedHtml.
|
||||
* @template ARG_TYPES, RETURN_TYPE
|
||||
*/
|
||||
goog.soy.Renderer.prototype.renderStrict = function(
|
||||
template, opt_templateData, opt_kind) {
|
||||
var result = template(
|
||||
opt_templateData || {}, undefined, this.getInjectedData_());
|
||||
goog.asserts.assertInstanceof(result, goog.soy.data.SanitizedContent,
|
||||
'renderStrict cannot be called on a non-strict soy template');
|
||||
goog.asserts.assert(
|
||||
result.contentKind ===
|
||||
(opt_kind || goog.soy.data.SanitizedContentKind.HTML),
|
||||
'renderStrict was called with the wrong kind of template');
|
||||
this.saveTemplateRender_(template, opt_templateData);
|
||||
this.handleRender();
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a strict Soy template of kind="html" and returns the result as
|
||||
* a goog.html.SafeHtml object.
|
||||
*
|
||||
* Rendering a template that is not a strict template of kind="html" results in
|
||||
* a runtime error.
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):
|
||||
* goog.soy.data.SanitizedContent} template The Soy template to render.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @return {!goog.html.SafeHtml}
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.Renderer.prototype.renderSafeHtml = function(
|
||||
template, opt_templateData) {
|
||||
var result = this.renderStrict(template, opt_templateData);
|
||||
return result.toSafeHtml();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.soy.Renderer.SavedTemplateRender} Saved template data for
|
||||
* the renders that have happened so far.
|
||||
*/
|
||||
goog.soy.Renderer.prototype.getSavedTemplateRenders = function() {
|
||||
return this.savedTemplateRenders_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Observes rendering of templates by this renderer.
|
||||
* @param {Node=} opt_node Relevant node, if available. The node may or may
|
||||
* not be in the document, depending on whether Soy is creating an element
|
||||
* or writing into an existing one.
|
||||
* @protected
|
||||
*/
|
||||
goog.soy.Renderer.prototype.handleRender = goog.nullFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Saves information about the current template render for debug purposes.
|
||||
* @param {Function} template The Soy template defining the element's content.
|
||||
* @param {Object=} opt_templateData The data for the template.
|
||||
* @private
|
||||
* @suppress {missingProperties} SoyJs compiler adds soyTemplateName to the
|
||||
* template.
|
||||
*/
|
||||
goog.soy.Renderer.prototype.saveTemplateRender_ = function(
|
||||
template, opt_templateData) {
|
||||
if (goog.DEBUG) {
|
||||
this.savedTemplateRenders_.push({
|
||||
template: template.soyTemplateName,
|
||||
data: opt_templateData,
|
||||
ijData: this.getInjectedData_()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the injectedParams map if necessary and calls the configuration
|
||||
* service to prepopulate it.
|
||||
* @return {Object} The injected params.
|
||||
* @private
|
||||
*/
|
||||
goog.soy.Renderer.prototype.getInjectedData_ = function() {
|
||||
return this.supplier_ ? this.supplier_.getData() : {};
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface for a supplier that provides Soy injected data.
|
||||
* @interface
|
||||
*/
|
||||
goog.soy.InjectedDataSupplier = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the injected data. Implementation may assume that
|
||||
* {@code goog.soy.Renderer} will treat the returned data as
|
||||
* immutable. The renderer will call this every time one of its
|
||||
* {@code render*} methods is called.
|
||||
* @return {Object} A key-value pair representing the injected data.
|
||||
*/
|
||||
goog.soy.InjectedDataSupplier.prototype.getData = function() {};
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: chrishenry@google.com (Chris Henry)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.soy.Renderer
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.soy.RendererTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.soy.RendererTest');
|
||||
goog.setTestOnly('goog.soy.RendererTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.i18n.bidi.Dir');
|
||||
goog.require('goog.soy.Renderer');
|
||||
goog.require('goog.soy.data.SanitizedContentKind');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('goog.soy.testHelper');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
var handleRender;
|
||||
|
||||
|
||||
function setUp() {
|
||||
// Replace the empty default implementation.
|
||||
handleRender = goog.soy.Renderer.prototype.handleRender =
|
||||
goog.testing.recordFunction(goog.soy.Renderer.prototype.handleRender);
|
||||
}
|
||||
|
||||
|
||||
var dataSupplier = {
|
||||
getData: function() {
|
||||
return {name: 'IjValue'};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function testRenderElement() {
|
||||
var testDiv = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
renderer.renderElement(
|
||||
testDiv, example.injectedDataTemplate, {name: 'Value'});
|
||||
assertEquals('ValueIjValue', elementToInnerHtml(testDiv));
|
||||
assertEquals(testDiv, handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
|
||||
function testRenderElementWithNoTemplateData() {
|
||||
var testDiv = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
renderer.renderElement(testDiv, example.noDataTemplate);
|
||||
assertEquals('<div>Hello</div>', elementToInnerHtml(testDiv));
|
||||
assertEquals(testDiv, handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsFragment() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
var fragment = renderer.renderAsFragment(
|
||||
example.injectedDataTemplate, {name: 'Value'});
|
||||
assertEquals('ValueIjValue', fragmentToHtml(fragment));
|
||||
assertEquals(fragment, handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsFragmentWithNoTemplateData() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
var fragment = renderer.renderAsFragment(example.noDataTemplate);
|
||||
assertEquals(goog.dom.NodeType.ELEMENT, fragment.nodeType);
|
||||
assertEquals('<div>Hello</div>', fragmentToHtml(fragment));
|
||||
assertEquals(fragment, handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsElement() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
var element = renderer.renderAsElement(
|
||||
example.injectedDataTemplate, {name: 'Value'});
|
||||
assertEquals('ValueIjValue', elementToInnerHtml(element));
|
||||
assertEquals(element, handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsElementWithNoTemplateData() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
var elem = renderer.renderAsElement(example.noDataTemplate);
|
||||
assertEquals('Hello', elementToInnerHtml(elem));
|
||||
assertEquals(elem, handleRender.getLastCall().getArguments()[0]);
|
||||
}
|
||||
|
||||
|
||||
function testRenderConvertsToString() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
assertEquals('Output should be a string',
|
||||
'Hello <b>World</b>', renderer.render(example.sanitizedHtmlTemplate));
|
||||
assertUndefined(handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
|
||||
function testRenderRejectsNonHtmlStrictTemplates() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
assertEquals(
|
||||
'Assertion failed: ' +
|
||||
'render was called with a strict template of kind other than "html"' +
|
||||
' (consider using renderText or renderStrict)',
|
||||
assertThrows(function() {
|
||||
renderer.render(example.unsanitizedTextTemplate, {});
|
||||
}).message);
|
||||
handleRender.assertCallCount(0);
|
||||
}
|
||||
|
||||
|
||||
function testRenderStrictDoesNotConvertToString() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
var result = renderer.renderStrict(example.sanitizedHtmlTemplate);
|
||||
assertEquals('Hello <b>World</b>', result.content);
|
||||
assertEquals(goog.soy.data.SanitizedContentKind.HTML, result.contentKind);
|
||||
assertUndefined(handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
|
||||
function testRenderStrictValidatesOutput() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
// Passes.
|
||||
renderer.renderStrict(example.sanitizedHtmlTemplate, {});
|
||||
// No SanitizedContent at all.
|
||||
assertEquals(
|
||||
'Assertion failed: ' +
|
||||
'renderStrict cannot be called on a non-strict soy template',
|
||||
assertThrows(function() {
|
||||
renderer.renderStrict(example.noDataTemplate, {});
|
||||
}).message);
|
||||
assertUndefined(handleRender.getLastCall().getArguments()[0]);
|
||||
// Passes.
|
||||
renderer.renderStrict(example.sanitizedHtmlTemplate, {},
|
||||
goog.soy.data.SanitizedContentKind.HTML);
|
||||
// Wrong content kind.
|
||||
assertEquals(
|
||||
'Assertion failed: ' +
|
||||
'renderStrict was called with the wrong kind of template',
|
||||
assertThrows(function() {
|
||||
renderer.renderStrict(example.sanitizedHtmlTemplate, {},
|
||||
goog.soy.data.SanitizedContentKind.JS);
|
||||
}).message);
|
||||
assertUndefined(handleRender.getLastCall().getArguments()[0]);
|
||||
|
||||
// renderStrict's opt_kind parameter defaults to SanitizedContentKind.HTML:
|
||||
// Passes.
|
||||
renderer.renderStrict(example.sanitizedHtmlTemplate, {});
|
||||
// Rendering non-HTML template fails:
|
||||
assertEquals(
|
||||
'Assertion failed: ' +
|
||||
'renderStrict was called with the wrong kind of template',
|
||||
assertThrows(function() {
|
||||
renderer.renderStrict(example.unsanitizedTextTemplate, {});
|
||||
}).message);
|
||||
assertUndefined(handleRender.getLastCall().getArguments()[0]);
|
||||
handleRender.assertCallCount(3);
|
||||
}
|
||||
|
||||
|
||||
function testRenderText() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
// RenderText converts to string.
|
||||
assertEquals('Output of renderText should be a string',
|
||||
'I <3 Puppies & Kittens',
|
||||
renderer.renderText(example.unsanitizedTextTemplate));
|
||||
assertUndefined(handleRender.getLastCall().getArguments()[0]);
|
||||
// RenderText on non-strict template fails.
|
||||
assertEquals(
|
||||
'Assertion failed: ' +
|
||||
'renderText cannot be called on a non-strict soy template',
|
||||
assertThrows(function() {
|
||||
renderer.renderText(example.noDataTemplate, {});
|
||||
}).message);
|
||||
// RenderText on non-text template fails.
|
||||
assertEquals(
|
||||
'Assertion failed: ' +
|
||||
'renderText was called with a template of kind other than "text"',
|
||||
assertThrows(function() {
|
||||
renderer.renderText(example.sanitizedHtmlTemplate, {});
|
||||
}).message);
|
||||
handleRender.assertCallCount(1);
|
||||
}
|
||||
|
||||
function testRenderSafeHtml() {
|
||||
var renderer = new goog.soy.Renderer(dataSupplier);
|
||||
var result = renderer.renderSafeHtml(example.sanitizedHtmlTemplate);
|
||||
assertEquals('Hello <b>World</b>', goog.html.SafeHtml.unwrap(result));
|
||||
assertEquals(goog.i18n.bidi.Dir.LTR, result.getDirection());
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides utility methods to render soy template.
|
||||
* @author chrishenry@google.com (Chris Henry)
|
||||
*/
|
||||
|
||||
goog.provide('goog.soy');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.soy.data.SanitizedContent');
|
||||
goog.require('goog.soy.data.SanitizedContentKind');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to require all Soy templates to be "strict html".
|
||||
* Soy templates that use strict autoescaping forbid noAutoescape along with
|
||||
* many dangerous directives, and return a runtime type SanitizedContent that
|
||||
* marks them as safe.
|
||||
*
|
||||
* If this flag is enabled, Soy templates will fail to render if a template
|
||||
* returns plain text -- indicating it is a non-strict template.
|
||||
*/
|
||||
goog.define('goog.soy.REQUIRE_STRICT_AUTOESCAPE', false);
|
||||
|
||||
|
||||
/**
|
||||
* Renders a Soy template and then set the output string as
|
||||
* the innerHTML of an element. It is recommended to use this helper function
|
||||
* instead of directly setting innerHTML in your hand-written code, so that it
|
||||
* will be easier to audit the code for cross-site scripting vulnerabilities.
|
||||
*
|
||||
* @param {Element} element The element whose content we are rendering into.
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):*} template
|
||||
* The Soy template defining the element's content.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @param {Object=} opt_injectedData The injected data for the template.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.renderElement = function(element, template, opt_templateData,
|
||||
opt_injectedData) {
|
||||
// Soy template parameter is only nullable for historical reasons.
|
||||
goog.asserts.assert(template, 'Soy template may not be null.');
|
||||
element.innerHTML = goog.soy.ensureTemplateOutputHtml_(template(
|
||||
opt_templateData || goog.soy.defaultTemplateData_, undefined,
|
||||
opt_injectedData));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a Soy template into a single node or a document
|
||||
* fragment. If the rendered HTML string represents a single node, then that
|
||||
* node is returned (note that this is *not* a fragment, despite them name of
|
||||
* the method). Otherwise a document fragment is returned containing the
|
||||
* rendered nodes.
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):*} template
|
||||
* The Soy template defining the element's content.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @param {Object=} opt_injectedData The injected data for the template.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper used to
|
||||
* create DOM nodes; defaults to {@code goog.dom.getDomHelper}.
|
||||
* @return {!Node} The resulting node or document fragment.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.renderAsFragment = function(template, opt_templateData,
|
||||
opt_injectedData, opt_domHelper) {
|
||||
// Soy template parameter is only nullable for historical reasons.
|
||||
goog.asserts.assert(template, 'Soy template may not be null.');
|
||||
var dom = opt_domHelper || goog.dom.getDomHelper();
|
||||
var html = goog.soy.ensureTemplateOutputHtml_(
|
||||
template(opt_templateData || goog.soy.defaultTemplateData_,
|
||||
undefined, opt_injectedData));
|
||||
goog.soy.assertFirstTagValid_(html);
|
||||
return dom.htmlToDocumentFragment(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders a Soy template into a single node. If the rendered
|
||||
* HTML string represents a single node, then that node is returned. Otherwise,
|
||||
* a DIV element is returned containing the rendered nodes.
|
||||
*
|
||||
* @param {null|function(ARG_TYPES, null=, Object<string, *>=):*} template
|
||||
* The Soy template defining the element's content.
|
||||
* @param {ARG_TYPES=} opt_templateData The data for the template.
|
||||
* @param {Object=} opt_injectedData The injected data for the template.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper used to
|
||||
* create DOM nodes; defaults to {@code goog.dom.getDomHelper}.
|
||||
* @return {!Element} Rendered template contents, wrapped in a parent DIV
|
||||
* element if necessary.
|
||||
* @template ARG_TYPES
|
||||
*/
|
||||
goog.soy.renderAsElement = function(template, opt_templateData,
|
||||
opt_injectedData, opt_domHelper) {
|
||||
// Soy template parameter is only nullable for historical reasons.
|
||||
goog.asserts.assert(template, 'Soy template may not be null.');
|
||||
var dom = opt_domHelper || goog.dom.getDomHelper();
|
||||
var wrapper = dom.createElement(goog.dom.TagName.DIV);
|
||||
var html = goog.soy.ensureTemplateOutputHtml_(template(
|
||||
opt_templateData || goog.soy.defaultTemplateData_,
|
||||
undefined, opt_injectedData));
|
||||
goog.soy.assertFirstTagValid_(html);
|
||||
wrapper.innerHTML = html;
|
||||
|
||||
// If the template renders as a single element, return it.
|
||||
if (wrapper.childNodes.length == 1) {
|
||||
var firstChild = wrapper.firstChild;
|
||||
if (firstChild.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
return /** @type {!Element} */ (firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, return the wrapper DIV.
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Ensures the result is "safe" to insert as HTML.
|
||||
*
|
||||
* Note if the template has non-strict autoescape, the guarantees here are very
|
||||
* weak. It is recommended applications switch to requiring strict
|
||||
* autoescaping over time by tweaking goog.soy.REQUIRE_STRICT_AUTOESCAPE.
|
||||
*
|
||||
* In the case the argument is a SanitizedContent object, it either must
|
||||
* already be of kind HTML, or if it is kind="text", the output will be HTML
|
||||
* escaped.
|
||||
*
|
||||
* @param {*} templateResult The template result.
|
||||
* @return {string} The assumed-safe HTML output string.
|
||||
* @private
|
||||
*/
|
||||
goog.soy.ensureTemplateOutputHtml_ = function(templateResult) {
|
||||
// Allow strings as long as strict autoescaping is not mandated. Note we
|
||||
// allow everything that isn't an object, because some non-escaping templates
|
||||
// end up returning non-strings if their only print statement is a
|
||||
// non-escaped argument, plus some unit tests spoof templates.
|
||||
// TODO(gboyer): Track down and fix these cases.
|
||||
if (!goog.soy.REQUIRE_STRICT_AUTOESCAPE && !goog.isObject(templateResult)) {
|
||||
return String(templateResult);
|
||||
}
|
||||
|
||||
// Allow SanitizedContent of kind HTML.
|
||||
if (templateResult instanceof goog.soy.data.SanitizedContent) {
|
||||
templateResult = /** @type {!goog.soy.data.SanitizedContent} */ (
|
||||
templateResult);
|
||||
var ContentKind = goog.soy.data.SanitizedContentKind;
|
||||
if (templateResult.contentKind === ContentKind.HTML) {
|
||||
return goog.asserts.assertString(templateResult.getContent());
|
||||
}
|
||||
if (templateResult.contentKind === ContentKind.TEXT) {
|
||||
// Allow text to be rendered, as long as we escape it. Other content
|
||||
// kinds will fail, since we don't know what to do with them.
|
||||
// TODO(gboyer): Perhaps also include URI in this case.
|
||||
return goog.string.htmlEscape(templateResult.getContent());
|
||||
}
|
||||
}
|
||||
|
||||
goog.asserts.fail('Soy template output is unsafe for use as HTML: ' +
|
||||
templateResult);
|
||||
|
||||
// In production, return a safe string, rather than failing hard.
|
||||
return 'zSoyz';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks that the rendered HTML does not start with an invalid tag that would
|
||||
* likely cause unexpected output from renderAsElement or renderAsFragment.
|
||||
* See {@link http://www.w3.org/TR/html5/semantics.html#semantics} for reference
|
||||
* as to which HTML elements can be parents of each other.
|
||||
* @param {string} html The output of a template.
|
||||
* @private
|
||||
*/
|
||||
goog.soy.assertFirstTagValid_ = function(html) {
|
||||
if (goog.asserts.ENABLE_ASSERTS) {
|
||||
var matches = html.match(goog.soy.INVALID_TAG_TO_RENDER_);
|
||||
goog.asserts.assert(!matches, 'This template starts with a %s, which ' +
|
||||
'cannot be a child of a <div>, as required by soy internals. ' +
|
||||
'Consider using goog.soy.renderElement instead.\nTemplate output: %s',
|
||||
matches && matches[0], html);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A pattern to find templates that cannot be rendered by renderAsElement or
|
||||
* renderAsFragment, as these elements cannot exist as the child of a <div>.
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.soy.INVALID_TAG_TO_RENDER_ =
|
||||
/^<(body|caption|col|colgroup|head|html|tr|td|tbody|thead|tfoot)>/i;
|
||||
|
||||
|
||||
/**
|
||||
* Immutable object that is passed into templates that are rendered
|
||||
* without any data.
|
||||
* @private @const
|
||||
*/
|
||||
goog.soy.defaultTemplateData_ = {};
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: chrishenry@google.com (Chris Henry)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.soy
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.soyTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.soyTest');
|
||||
goog.setTestOnly('goog.soyTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.soy');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('goog.soy.testHelper');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var stubs;
|
||||
|
||||
function setUp() {
|
||||
stubs = new goog.testing.PropertyReplacer();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
function testRenderElement() {
|
||||
var testDiv = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
goog.soy.renderElement(testDiv, example.multiRootTemplate, {name: 'Boo'});
|
||||
assertEquals('<div>Hello</div><div>Boo</div>', elementToInnerHtml(testDiv));
|
||||
}
|
||||
|
||||
|
||||
function testRenderElementWithNoTemplateData() {
|
||||
var testDiv = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
goog.soy.renderElement(testDiv, example.noDataTemplate);
|
||||
assertEquals('<div>Hello</div>', elementToInnerHtml(testDiv));
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsFragmentTextNode() {
|
||||
var fragment = goog.soy.renderAsFragment(
|
||||
example.textNodeTemplate, {name: 'Boo'});
|
||||
assertEquals(goog.dom.NodeType.TEXT, fragment.nodeType);
|
||||
assertEquals('Boo', fragmentToHtml(fragment));
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsFragmentInjectedData() {
|
||||
var fragment = goog.soy.renderAsFragment(example.injectedDataTemplate,
|
||||
{name: 'Boo'}, {name: 'ijBoo'});
|
||||
assertEquals(goog.dom.NodeType.TEXT, fragment.nodeType);
|
||||
assertEquals('BooijBoo', fragmentToHtml(fragment));
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsFragmentSingleRoot() {
|
||||
var fragment = goog.soy.renderAsFragment(
|
||||
example.singleRootTemplate, {name: 'Boo'});
|
||||
assertEquals(goog.dom.NodeType.ELEMENT, fragment.nodeType);
|
||||
assertEquals(goog.dom.TagName.SPAN, fragment.tagName);
|
||||
assertEquals('Boo', fragment.innerHTML);
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsFragmentMultiRoot() {
|
||||
var fragment = goog.soy.renderAsFragment(
|
||||
example.multiRootTemplate, {name: 'Boo'});
|
||||
assertEquals(goog.dom.NodeType.DOCUMENT_FRAGMENT, fragment.nodeType);
|
||||
assertEquals('<div>Hello</div><div>Boo</div>', fragmentToHtml(fragment));
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsFragmentNoData() {
|
||||
var fragment = goog.soy.renderAsFragment(example.noDataTemplate);
|
||||
assertEquals(goog.dom.NodeType.ELEMENT, fragment.nodeType);
|
||||
assertEquals('<div>Hello</div>', fragmentToHtml(fragment));
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsElementTextNode() {
|
||||
var elem = goog.soy.renderAsElement(example.textNodeTemplate, {name: 'Boo'});
|
||||
assertEquals(goog.dom.NodeType.ELEMENT, elem.nodeType);
|
||||
assertEquals(goog.dom.TagName.DIV, elem.tagName);
|
||||
assertEquals('Boo', elementToInnerHtml(elem));
|
||||
}
|
||||
|
||||
function testRenderAsElementInjectedData() {
|
||||
var elem = goog.soy.renderAsElement(example.injectedDataTemplate,
|
||||
{name: 'Boo'}, {name: 'ijBoo'});
|
||||
assertEquals(goog.dom.NodeType.ELEMENT, elem.nodeType);
|
||||
assertEquals(goog.dom.TagName.DIV, elem.tagName);
|
||||
assertEquals('BooijBoo', elementToInnerHtml(elem));
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsElementSingleRoot() {
|
||||
var elem = goog.soy.renderAsElement(
|
||||
example.singleRootTemplate, {name: 'Boo'});
|
||||
assertEquals(goog.dom.NodeType.ELEMENT, elem.nodeType);
|
||||
assertEquals(goog.dom.TagName.SPAN, elem.tagName);
|
||||
assertEquals('Boo', elementToInnerHtml(elem));
|
||||
}
|
||||
|
||||
|
||||
function testRenderAsElementMultiRoot() {
|
||||
var elem = goog.soy.renderAsElement(example.multiRootTemplate, {name: 'Boo'});
|
||||
assertEquals(goog.dom.NodeType.ELEMENT, elem.nodeType);
|
||||
assertEquals(goog.dom.TagName.DIV, elem.tagName);
|
||||
assertEquals('<div>Hello</div><div>Boo</div>', elementToInnerHtml(elem));
|
||||
}
|
||||
|
||||
function testRenderAsElementWithNoData() {
|
||||
var elem = goog.soy.renderAsElement(example.noDataTemplate);
|
||||
assertEquals('Hello', elementToInnerHtml(elem));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the function throws an error for unsafe templates.
|
||||
* @param {Function} function Callback to test.
|
||||
*/
|
||||
function assertUnsafeTemplateOutputErrorThrown(func) {
|
||||
stubs.set(goog.asserts, 'ENABLE_ASSERTS', true);
|
||||
assertContains('Soy template output is unsafe for use as HTML',
|
||||
assertThrows(func).message);
|
||||
stubs.set(goog.asserts, 'ENABLE_ASSERTS', false);
|
||||
assertEquals('zSoyz', func());
|
||||
}
|
||||
|
||||
function testAllowButEscapeUnsanitizedText() {
|
||||
var div = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
goog.soy.renderElement(div, example.unsanitizedTextTemplate);
|
||||
assertEquals('I <3 Puppies & Kittens', div.innerHTML);
|
||||
var fragment = goog.soy.renderAsFragment(example.unsanitizedTextTemplate);
|
||||
assertEquals('I <3 Puppies & Kittens', fragment.nodeValue);
|
||||
assertEquals('I <3 Puppies & Kittens',
|
||||
goog.soy.renderAsElement(example.unsanitizedTextTemplate).innerHTML);
|
||||
}
|
||||
|
||||
function testRejectSanitizedCss() {
|
||||
assertUnsafeTemplateOutputErrorThrown(function() {
|
||||
goog.soy.renderAsElement(example.sanitizedCssTemplate);
|
||||
});
|
||||
}
|
||||
|
||||
function testRejectSanitizedCss() {
|
||||
assertUnsafeTemplateOutputErrorThrown(function() {
|
||||
return goog.soy.renderAsElement(
|
||||
example.templateSpoofingSanitizedContentString).innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
function testRejectStringTemplatesWhenModeIsSet() {
|
||||
stubs.set(goog.soy, 'REQUIRE_STRICT_AUTOESCAPE', true);
|
||||
assertUnsafeTemplateOutputErrorThrown(function() {
|
||||
return goog.soy.renderAsElement(example.noDataTemplate).innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
function testAcceptSanitizedHtml() {
|
||||
assertEquals('Hello World', goog.dom.getTextContent(
|
||||
goog.soy.renderAsElement(example.sanitizedHtmlTemplate)));
|
||||
}
|
||||
|
||||
function testRejectSanitizedHtmlAttributes() {
|
||||
// Attributes context has nothing to do with html.
|
||||
assertUnsafeTemplateOutputErrorThrown(function() {
|
||||
return goog.dom.getTextContent(
|
||||
goog.soy.renderAsElement(example.sanitizedHtmlAttributesTemplate));
|
||||
});
|
||||
}
|
||||
|
||||
function testAcceptNonObject() {
|
||||
// Some templates, or things that spoof templates in unit tests, might return
|
||||
// non-strings in unusual cases.
|
||||
assertEquals('null', goog.dom.getTextContent(
|
||||
goog.soy.renderAsElement(goog.functions.constant(null))));
|
||||
}
|
||||
|
||||
function testDebugAssertionWithBadFirstTag() {
|
||||
try {
|
||||
goog.soy.renderAsElement(example.tableRowTemplate);
|
||||
// Expect no exception in production code.
|
||||
assert(!goog.DEBUG);
|
||||
} catch (e) {
|
||||
// Expect exception in debug code.
|
||||
assert(goog.DEBUG);
|
||||
// Make sure to let the developer know which tag caused the problem.
|
||||
assertContains('<tr>', e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
goog.soy.renderAsFragment(example.tableRowTemplate);
|
||||
// Expect no exception in production code.
|
||||
assert(!goog.DEBUG);
|
||||
} catch (e) {
|
||||
// Expect exception in debug code.
|
||||
assert(goog.DEBUG);
|
||||
// Make sure to let the developer know which tag caused the problem.
|
||||
assertContains('<tr>', e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
goog.soy.renderAsElement(example.colGroupTemplateCaps);
|
||||
// Expect no exception in production code.
|
||||
assert(!goog.DEBUG);
|
||||
} catch (e) {
|
||||
// Expect exception in debug code.
|
||||
assert(goog.DEBUG);
|
||||
// Make sure to let the developer know which tag caused the problem.
|
||||
assertContains('<COLGROUP>', e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides test helpers for Soy tests.
|
||||
* @author chrishenry@google.com (Chris Henry)
|
||||
*/
|
||||
|
||||
/** @suppress {extraProvide} */
|
||||
goog.provide('goog.soy.testHelper');
|
||||
goog.setTestOnly('goog.soy.testHelper');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.i18n.bidi.Dir');
|
||||
goog.require('goog.soy.data.SanitizedContent');
|
||||
goog.require('goog.soy.data.SanitizedContentKind');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Instantiable subclass of SanitizedContent.
|
||||
*
|
||||
* This is a spoof for sanitized content that isn't robust enough to get
|
||||
* through Soy's escaping functions but is good enough for the checks here.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} content The text.
|
||||
* @param {goog.soy.data.SanitizedContentKind} kind The kind of safe content.
|
||||
* @extends {goog.soy.data.SanitizedContent}
|
||||
*/
|
||||
function SanitizedContentSubclass(content, kind) {
|
||||
// IMPORTANT! No superclass chaining to avoid exception being thrown.
|
||||
this.content = content;
|
||||
this.contentKind = kind;
|
||||
}
|
||||
goog.inherits(SanitizedContentSubclass, goog.soy.data.SanitizedContent);
|
||||
|
||||
|
||||
function makeSanitizedContent(content, kind) {
|
||||
return new SanitizedContentSubclass(content, kind);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Fake Soy-generated template functions.
|
||||
//
|
||||
|
||||
var example = {};
|
||||
|
||||
|
||||
example.textNodeTemplate = function(opt_data, opt_sb, opt_injectedData) {
|
||||
assertNotNull(opt_data);
|
||||
assertNotUndefined(opt_data);
|
||||
return goog.string.htmlEscape(opt_data.name);
|
||||
};
|
||||
|
||||
|
||||
example.singleRootTemplate = function(opt_data, opt_sb, opt_injectedData) {
|
||||
assertNotNull(opt_data);
|
||||
assertNotUndefined(opt_data);
|
||||
return '<span>' + goog.string.htmlEscape(opt_data.name) + '</span>';
|
||||
};
|
||||
|
||||
|
||||
example.multiRootTemplate = function(opt_data, opt_sb, opt_injectedData) {
|
||||
assertNotNull(opt_data);
|
||||
assertNotUndefined(opt_data);
|
||||
return '<div>Hello</div><div>' + goog.string.htmlEscape(opt_data.name) +
|
||||
'</div>';
|
||||
};
|
||||
|
||||
|
||||
example.injectedDataTemplate = function(opt_data, opt_sb, opt_injectedData) {
|
||||
assertNotNull(opt_data);
|
||||
assertNotUndefined(opt_data);
|
||||
return goog.string.htmlEscape(opt_data.name) +
|
||||
goog.string.htmlEscape(opt_injectedData.name);
|
||||
};
|
||||
|
||||
|
||||
example.noDataTemplate = function(opt_data, opt_sb, opt_injectedData) {
|
||||
assertNotNull(opt_data);
|
||||
assertNotUndefined(opt_data);
|
||||
return '<div>Hello</div>';
|
||||
};
|
||||
|
||||
|
||||
example.sanitizedHtmlTemplate = function(opt_data, opt_sb, opt_injectedData) {
|
||||
// Test the SanitizedContent constructor.
|
||||
var sanitized = makeSanitizedContent('Hello <b>World</b>',
|
||||
goog.soy.data.SanitizedContentKind.HTML);
|
||||
sanitized.contentDir = goog.i18n.bidi.Dir.LTR;
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
|
||||
example.sanitizedHtmlAttributesTemplate =
|
||||
function(opt_data, opt_sb, opt_injectedData) {
|
||||
return makeSanitizedContent('foo="bar"',
|
||||
goog.soy.data.SanitizedContentKind.ATTRIBUTES);
|
||||
};
|
||||
|
||||
|
||||
example.sanitizedCssTemplate =
|
||||
function(opt_data, opt_sb, opt_injectedData) {
|
||||
return makeSanitizedContent('display:none',
|
||||
goog.soy.data.SanitizedContentKind.CSS);
|
||||
};
|
||||
|
||||
|
||||
example.unsanitizedTextTemplate =
|
||||
function(opt_data, opt_sb, opt_injectedData) {
|
||||
return makeSanitizedContent('I <3 Puppies & Kittens',
|
||||
goog.soy.data.SanitizedContentKind.TEXT);
|
||||
};
|
||||
|
||||
|
||||
example.templateSpoofingSanitizedContentString =
|
||||
function(opt_data, opt_sb, opt_injectedData) {
|
||||
return makeSanitizedContent('Hello World',
|
||||
// This is to ensure we're using triple-equals against a unique Javascript
|
||||
// object. For example, in Javascript, consider ({}) == '[Object object]'
|
||||
// is true.
|
||||
goog.soy.data.SanitizedContentKind.HTML.toString());
|
||||
};
|
||||
|
||||
|
||||
example.tableRowTemplate = function(opt_data, opt_sb, opt_injectedData) {
|
||||
return '<tr><td></td></tr>';
|
||||
};
|
||||
|
||||
|
||||
example.colGroupTemplateCaps = function(opt_data, opt_sb, opt_injectedData) {
|
||||
return '<COLGROUP></COLGROUP>';
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Test helper functions.
|
||||
//
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the content of document fragment as HTML.
|
||||
* @param {Node} fragment The document fragment.
|
||||
* @return {string} Content of the document fragment as HTML.
|
||||
*/
|
||||
function fragmentToHtml(fragment) {
|
||||
var testDiv = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
testDiv.appendChild(fragment);
|
||||
return elementToInnerHtml(testDiv);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the content of an element as HTML.
|
||||
* @param {Element} elem The element.
|
||||
* @return {string} Content of the element as HTML.
|
||||
*/
|
||||
function elementToInnerHtml(elem) {
|
||||
var innerHtml = elem.innerHTML;
|
||||
if (goog.userAgent.IE) {
|
||||
innerHtml = innerHtml.replace(/DIV/g, 'div').replace(/\s/g, '');
|
||||
}
|
||||
return innerHtml;
|
||||
}
|
||||
Reference in New Issue
Block a user