diff --git a/FEATURES.md b/FEATURES.md
index 4aceb8f0f22..2367449c356 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -44,5 +44,17 @@ for a detailed explanation.
* `ember-runtime-enumerable-includes`
-Deprecates `Enumerable#contains` and `Array#contains` in favor of `Enumerable#includes` and `Array#includes`
+Deprecates `Enumerable#contains` and `Array#contains` in favor of `Enumerable#includes` and `Array#includes`
to stay in line with ES standards (see [RFC](https://github.com/emberjs/rfcs/blob/master/text/0136-contains-to-includes.md)).
+
+* `ember-string-ishtmlsafe`
+
+ Introduces an API to detect if strings are decorated as htmlSafe. Example:
+
+ ```javascript
+ var plainString = 'plain string',
+ safeString = Ember.String.htmlSafe('
someValue
');
+
+ Ember.String.isHtmlSafe(plainString); // false
+ Ember.String.isHtmlSafe(safeString); // true
+ ```
diff --git a/features.json b/features.json
index 38503f53568..46025e329a6 100644
--- a/features.json
+++ b/features.json
@@ -8,6 +8,7 @@
"ember-glimmer": null,
"ember-runtime-computed-uniq-by": true,
"ember-improved-instrumentation": null,
- "ember-runtime-enumerable-includes": null
+ "ember-runtime-enumerable-includes": null,
+ "ember-string-ishtmlsafe": null
}
}
diff --git a/packages/ember-glimmer/tests/compat/safe-string-test.js b/packages/ember-glimmer/tests/compat/safe-string-test.js
new file mode 100644
index 00000000000..fd1b600395c
--- /dev/null
+++ b/packages/ember-glimmer/tests/compat/safe-string-test.js
@@ -0,0 +1,32 @@
+import EmberHandlebars from 'ember-htmlbars/compat';
+import { isHtmlSafe } from 'ember-htmlbars/utils/string';
+import { TestCase } from '../utils/abstract-test-case';
+import { moduleFor } from '../utils/test-case';
+
+
+moduleFor('compat - SafeString', class extends TestCase {
+ ['@test using new results in a deprecation']() {
+ let result;
+
+ expectDeprecation(() => {
+ result = new EmberHandlebars.SafeString('test');
+ }, 'Ember.Handlebars.SafeString is deprecated in favor of Ember.String.htmlSafe');
+
+ this.assert.equal(result.toHTML(), 'test');
+
+ // Ensure this functionality is maintained for backwards compat, but also deprecated.
+ expectDeprecation(() => {
+ this.assert.ok(result instanceof EmberHandlebars.SafeString);
+ }, 'Ember.Handlebars.SafeString is deprecated in favor of Ember.String.htmlSafe');
+ }
+
+ ['@test isHtmlSafe should detect SafeString']() {
+ let safeString;
+
+ expectDeprecation(() => {
+ safeString = new EmberHandlebars.SafeString('test');
+ }, 'Ember.Handlebars.SafeString is deprecated in favor of Ember.String.htmlSafe');
+
+ this.assert.ok(isHtmlSafe(safeString));
+ }
+});
diff --git a/packages/ember-glimmer/tests/utils/string-test.js b/packages/ember-glimmer/tests/utils/string-test.js
new file mode 100644
index 00000000000..770d79bf3fa
--- /dev/null
+++ b/packages/ember-glimmer/tests/utils/string-test.js
@@ -0,0 +1,45 @@
+import SafeString from 'htmlbars-util/safe-string';
+import { htmlSafe, isHtmlSafe } from 'ember-htmlbars/utils/string';
+import isEnabled from 'ember-metal/features';
+import { TestCase } from './abstract-test-case';
+import { moduleFor } from './test-case';
+
+moduleFor('SafeString', class extends TestCase {
+ ['@test htmlSafe should return an instance of SafeString']() {
+ let safeString = htmlSafe('you need to be more bold');
+
+ this.assert.ok(safeString instanceof SafeString, 'should be a SafeString');
+ }
+
+ ['@test htmlSafe should return an empty string for null']() {
+ let safeString = htmlSafe(null);
+
+ this.assert.equal(safeString instanceof SafeString, true, 'should be a SafeString');
+ this.assert.equal(safeString.toString(), '', 'should return an empty string');
+ }
+
+ ['@test htmlSafe should return an instance of SafeString']() {
+ let safeString = htmlSafe();
+
+ this.assert.equal(safeString instanceof SafeString, true, 'should be a SafeString');
+ this.assert.equal(safeString.toString(), '', 'should return an empty string');
+ }
+});
+
+if (isEnabled('ember-string-ishtmlsafe')) {
+ moduleFor('SafeString isHtmlSafe', class extends TestCase {
+ ['@test isHtmlSafe should detect SafeString']() {
+ let safeString = htmlSafe('Emphasize the important things.');
+
+ this.assert.ok(isHtmlSafe(safeString));
+ }
+
+ ['@test isHtmlSafe should not detect SafeString on primatives']() {
+ this.assert.notOk(isHtmlSafe('Hello World'));
+ this.assert.notOk(isHtmlSafe({}));
+ this.assert.notOk(isHtmlSafe([]));
+ this.assert.notOk(isHtmlSafe(10));
+ this.assert.notOk(isHtmlSafe(null));
+ }
+ });
+}
diff --git a/packages/ember-htmlbars/lib/compat.js b/packages/ember-htmlbars/lib/compat.js
index aadd0790049..457f8abfc12 100644
--- a/packages/ember-htmlbars/lib/compat.js
+++ b/packages/ember-htmlbars/lib/compat.js
@@ -1,12 +1,27 @@
import Ember from 'ember-metal/core'; // for Handlebars export
+import { deprecate } from 'ember-metal/debug';
import {
SafeString,
escapeExpression
} from 'ember-htmlbars/utils/string';
-const EmberHandlebars = Ember.Handlebars = Ember.Handlebars || {};
+let EmberHandlebars = Ember.Handlebars = Ember.Handlebars || {};
+Object.defineProperty(EmberHandlebars, 'SafeString', {
+ get() {
+ deprecate(
+ 'Ember.Handlebars.SafeString is deprecated in favor of Ember.String.htmlSafe',
+ false,
+ {
+ id: 'ember-htmlbars.ember-handlebars-safestring',
+ until: '3.0.0',
+ url: 'http://emberjs.com/deprecations/v2.x#toc_use-ember-string-htmlsafe-over-ember-handlebars-safestring'
+ }
+ );
+
+ return SafeString;
+ }
+});
-EmberHandlebars.SafeString = SafeString;
EmberHandlebars.Utils = {
escapeExpression: escapeExpression
};
diff --git a/packages/ember-htmlbars/lib/utils/string.js b/packages/ember-htmlbars/lib/utils/string.js
index 931b20cb8ca..2ac45d4e1ce 100644
--- a/packages/ember-htmlbars/lib/utils/string.js
+++ b/packages/ember-htmlbars/lib/utils/string.js
@@ -6,6 +6,7 @@
import { ENV } from 'ember-environment';
import EmberStringUtils from 'ember-runtime/system/string';
import { SafeString, escapeExpression } from 'htmlbars-util';
+import isEnabled from 'ember-metal/features';
/**
Mark a string as safe for unescaped output with Ember templates. If you
@@ -38,8 +39,34 @@ if (ENV.EXTEND_PROTOTYPES.String) {
};
}
+/**
+ Detects if a string was decorated using `Ember.String.htmlSafe`.
+
+ ```javascript
+ var plainString = 'plain string',
+ safeString = Ember.String.htmlSafe('someValue
');
+
+ Ember.String.isHtmlSafe(plainString); // false
+ Ember.String.isHtmlSafe(safeString); // true
+ ```
+
+ @method isHtmlSafe
+ @for Ember.String
+ @static
+ @return {Boolean} `true` if the string was decorated with `htmlSafe`, `false` otherwise.
+ @public
+*/
+function isHtmlSafe(str) {
+ return str && typeof str.toHTML === 'function';
+}
+
+if (isEnabled('ember-string-ishtmlsafe')) {
+ EmberStringUtils.isHtmlSafe = isHtmlSafe;
+}
+
export {
SafeString,
htmlSafe,
+ isHtmlSafe,
escapeExpression
};
diff --git a/packages/ember-htmlbars/tests/compat/safe-string-test.js b/packages/ember-htmlbars/tests/compat/safe-string-test.js
new file mode 120000
index 00000000000..0da6de8626a
--- /dev/null
+++ b/packages/ember-htmlbars/tests/compat/safe-string-test.js
@@ -0,0 +1 @@
+../../../ember-glimmer/tests/compat/safe-string-test.js
\ No newline at end of file
diff --git a/packages/ember-htmlbars/tests/utils/string-test.js b/packages/ember-htmlbars/tests/utils/string-test.js
new file mode 120000
index 00000000000..abe85d22cb5
--- /dev/null
+++ b/packages/ember-htmlbars/tests/utils/string-test.js
@@ -0,0 +1 @@
+../../../ember-glimmer/tests/utils/string-test.js
\ No newline at end of file
diff --git a/packages/ember-htmlbars/tests/utils/string_test.js b/packages/ember-htmlbars/tests/utils/string_test.js
deleted file mode 100644
index 4aa0f0e533f..00000000000
--- a/packages/ember-htmlbars/tests/utils/string_test.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import SafeString from 'htmlbars-util/safe-string';
-import { htmlSafe } from 'ember-htmlbars/utils/string';
-
-QUnit.module('ember-htmlbars: SafeString');
-
-QUnit.test('htmlSafe should return an instance of SafeString', function() {
- let safeString = htmlSafe('you need to be more bold');
-
- ok(safeString instanceof SafeString, 'should be a SafeString');
-});
-
-QUnit.test('htmlSafe should return an empty string for null', function() {
- let safeString = htmlSafe(null);
-
- equal(safeString instanceof SafeString, true, 'should be a SafeString');
- equal(safeString.toString(), '', 'should return an empty string');
-});
-
-QUnit.test('htmlSafe should return an empty string for undefined', function() {
- let safeString = htmlSafe();
-
- equal(safeString instanceof SafeString, true, 'should be a SafeString');
- equal(safeString.toString(), '', 'should return an empty string');
-});