Permalink
Browse files

Merge branch 'master' into 144-fix-template-spec

  • Loading branch information...
2 parents c666cb5 + 0977fc0 commit a3afdeb0027cfa032631dccdb64ca294870c2ca8 @SteveSanderson SteveSanderson committed May 18, 2012
View
@@ -0,0 +1,8 @@
+language: node_js
+before_script:
+ - "cd build; rm output/*.*"
+ - "./build-linux"
+ - "cd .."
+script:
+ - "phantomjs spec/runner.phantom.js"
+
View
@@ -15,25 +15,25 @@ SourceFiles=`grep js < fragments/source-references.js | # Find JS references
sed "s/[ \',]//g" | # Strip off JSON fluff (whitespace, commas, quotes)
sed -e 's/.*/..\/&/' | # Fix the paths by prefixing with ../
tr '\n' ' '` # Combine into single line
-cat fragments/amd-pre.js > $OutDebugFile.temp
+cat fragments/extern-pre.js > $OutDebugFile.temp
+cat fragments/amd-pre.js >> $OutDebugFile.temp
cat $SourceFiles >> $OutDebugFile.temp
cat fragments/amd-post.js >> $OutDebugFile.temp
+cat fragments/extern-post.js >> $OutDebugFile.temp
# Now call Google Closure Compiler to produce a minified version
-curl -d output_info=compiled_code -d output_format=text -d compilation_level=ADVANCED_OPTIMIZATIONS --data-urlencode "js_code=/**@const*/var DEBUG=false;" --data-urlencode js_code@$OutDebugFile.temp "http://closure-compiler.appspot.com/compile" > $OutMinFile.temp
+curl -d output_info=compiled_code -d output_format=text -d compilation_level=ADVANCED_OPTIMIZATIONS --data-urlencode output_wrapper="(function() {%output%})();" --data-urlencode "js_code=/**@const*/var DEBUG=false;" --data-urlencode js_code@$OutDebugFile.temp "http://closure-compiler.appspot.com/compile" > $OutMinFile.temp
# Finalise each file by prefixing with version header and surrounding in function closure
cp fragments/version-header.js $OutDebugFile
-echo "(function(window,document,navigator,undefined){" >> $OutDebugFile
-echo "var DEBUG=true;" >> $OutDebugFile
-cat $OutDebugFile.temp >> $OutDebugFile
-echo "})(window,document,navigator);" >> $OutDebugFile
+echo "(function(){" >> $OutDebugFile
+echo "var DEBUG=true;" >> $OutDebugFile
+cat $OutDebugFile.temp >> $OutDebugFile
+echo "})();" >> $OutDebugFile
rm $OutDebugFile.temp
cp fragments/version-header.js $OutMinFile
-echo "(function(window,document,navigator,undefined){" >> $OutMinFile
-cat $OutMinFile.temp >> $OutMinFile
-echo "})(window,document,navigator);" >> $OutMinFile
+cat $OutMinFile.temp >> $OutMinFile
rm $OutMinFile.temp
# Inject the version number string
View
@@ -18,25 +18,25 @@ goto :Combine
goto :EOF
:Combine
-type fragments\amd-pre.js > %OutDebugFile%.temp
+type fragments\extern-pre.js > %OutDebugFile%.temp
+type fragments\amd-pre.js >> %OutDebugFile%.temp
type %AllFiles% >> %OutDebugFile%.temp 2>nul
type fragments\amd-post.js >> %OutDebugFile%.temp
+type fragments\extern-post.js >> %OutDebugFile%.temp
@rem Now call Google Closure Compiler to produce a minified version
-tools\curl -d output_info=compiled_code -d output_format=text -d compilation_level=ADVANCED_OPTIMIZATIONS --data-urlencode "js_code=/**@const*/var DEBUG=false;" --data-urlencode js_code@%OutDebugFile%.temp "http://closure-compiler.appspot.com/compile" > %OutMinFile%.temp
+tools\curl -d output_info=compiled_code -d output_format=text -d compilation_level=ADVANCED_OPTIMIZATIONS --data-urlencode output_wrapper="(function() {%%output%%})();" --data-urlencode "js_code=/**@const*/var DEBUG=false;" --data-urlencode js_code@%OutDebugFile%.temp "http://closure-compiler.appspot.com/compile" > %OutMinFile%.temp
@rem Finalise each file by prefixing with version header and surrounding in function closure
copy /y fragments\version-header.js %OutDebugFile% >nul
-echo (function(window,document,navigator,undefined){>> %OutDebugFile%
+echo (function(){>> %OutDebugFile%
echo var DEBUG=true;>> %OutDebugFile%
-type %OutDebugFile%.temp >> %OutDebugFile%
-echo })(window,document,navigator);>> %OutDebugFile%
+type %OutDebugFile%.temp >> %OutDebugFile%
+echo })();>> %OutDebugFile%
del %OutDebugFile%.temp
copy /y fragments\version-header.js %OutMinFile% >nul
-echo (function(window,document,navigator,undefined){>> %OutMinFile%
-type %OutMinFile%.temp >> %OutMinFile%
-echo })(window,document,navigator);>> %OutMinFile%
+type %OutMinFile%.temp >> %OutMinFile%
del %OutMinFile%.temp
@rem Inject the version number string
@@ -0,0 +1 @@
+})(window,document,navigator,window["jQuery"]);
@@ -0,0 +1 @@
+(function(window,document,navigator,jQuery,undefined){
View
No changes.
@@ -379,6 +379,33 @@ describe('Binding: Value', {
value_of(observable()).should_be(20);
},
+ 'For select boxes with values attributes, should always use value (and not text)': function() {
+ var observable = new ko.observable('A');
+ testNode.innerHTML = "<select data-bind='value:myObservable'><option value=''>A</option><option value='A'>B</option></select>";
+ ko.applyBindings({ myObservable: observable }, testNode);
+ var dropdown = testNode.childNodes[0];
+ value_of(dropdown.selectedIndex).should_be(1);
+
+ dropdown.selectedIndex = 0;
+ ko.utils.triggerEvent(dropdown, "change");
+ value_of(observable()).should_be("");
+ },
+
+ 'For select boxes with text values but no value property, should use text value': function() {
+ var observable = new ko.observable('B');
+ testNode.innerHTML = "<select data-bind='value:myObservable'><option>A</option><option>B</option><option>C</option></select>";
+ ko.applyBindings({ myObservable: observable }, testNode);
+ var dropdown = testNode.childNodes[0];
+ value_of(dropdown.selectedIndex).should_be(1);
+
+ dropdown.selectedIndex = 0;
+ ko.utils.triggerEvent(dropdown, "change");
+ value_of(observable()).should_be("A");
+
+ observable('C');
+ value_of(dropdown.selectedIndex).should_be(2);
+ },
+
'On IE, should respond exactly once to "propertychange" followed by "blur" or "change" or both': function() {
var isIE = navigator.userAgent.indexOf("MSIE") >= 0;
@@ -702,6 +729,32 @@ describe('Binding: CSS class name', {
value_of(testNode.childNodes[0].className).should_be("");
observable1(true);
value_of(testNode.childNodes[0].className).should_be("myRule");
+ },
+
+ 'Should toggle multiple CSS classes if specified as a single string separated by spaces': function() {
+ var observable1 = new ko.observable();
+ testNode.innerHTML = "<div class='unrelatedClass1' data-bind='css: { \"myRule _another-Rule123\": someModelProperty }'>Hallo</div>";
+ ko.applyBindings({ someModelProperty: observable1 }, testNode);
+
+ value_of(testNode.childNodes[0].className).should_be("unrelatedClass1");
+ observable1(true);
+ value_of(testNode.childNodes[0].className).should_be("unrelatedClass1 myRule _another-Rule123");
+ observable1(false);
+ value_of(testNode.childNodes[0].className).should_be("unrelatedClass1");
+ },
+
+ 'Should set/change dynamic CSS class(es) if string is specified': function() {
+ var observable1 = new ko.observable("");
+ testNode.innerHTML = "<div class='unrelatedClass1' data-bind='css: someModelProperty'>Hallo</div>";
+ ko.applyBindings({ someModelProperty: observable1 }, testNode);
+
+ value_of(testNode.childNodes[0].className).should_be("unrelatedClass1");
+ observable1("my-Rule");
+ value_of(testNode.childNodes[0].className).should_be("unrelatedClass1 my-Rule");
+ observable1("another_Rule my-Rule");
+ value_of(testNode.childNodes[0].className).should_be("unrelatedClass1 another_Rule my-Rule");
+ observable1(undefined);
+ value_of(testNode.childNodes[0].className).should_be("unrelatedClass1");
}
});
@@ -0,0 +1,38 @@
+JSSpec.DSL.Subject.prototype.should_be_one_of = function (expectedPossibilities) {
+ for (var i = 0; i < expectedPossibilities.length; i++) {
+ var matcher = JSSpec.EqualityMatcher.createInstance(expectedPossibilities[i], this.target);
+ if (matcher.matches())
+ return;
+ }
+ JSSpec._assertionFailure = { message: "Should be one of the values: " + expectedPossibilities.toString() + "; was: " + this.target.toString() };
+ throw JSSpec._assertionFailure;
+};
+
+JSSpec.DSL.Subject.prototype.should_contain = function (expected) {
+ if (this.target.indexOf(expected) >= 0)
+ return;
+ JSSpec._assertionFailure = { message: "Should contain: " + expected.toString() + "; was: " + this.target.toString() };
+ throw JSSpec._assertionFailure;
+};
+
+JSSpec.DSL.Subject.prototype.should_contain_html = function (expectedHtml) {
+ var cleanedHtml = this.target.innerHTML.toLowerCase().replace(/\r\n/g, "");
+ // IE < 9 strips whitespace immediately following comment nodes. Normalize by doing the same on all browsers.
+ cleanedHtml = cleanedHtml.replace(/(<!--.*?-->)\s*/g, "$1");
+ expectedHtml = expectedHtml.replace(/(<!--.*?-->)\s*/g, "$1");
+ // Also remove __ko__ expando properties (for DOM data) - most browsers hide these anyway but IE < 9 includes them in innerHTML
+ cleanedHtml = cleanedHtml.replace(/ __ko__\d+=\"(ko\d+|null)\"/g, "");
+ JSSpec.DSL.Subject.prototype.should_be.call({ target: cleanedHtml }, expectedHtml);
+};
+
+JSSpec.DSL.Subject.prototype.should_contain_text = function (expectedText) {
+ var actualText = 'textContent' in this.target ? this.target.textContent : this.target.innerText;
+ var cleanedActualText = actualText.replace(/\r\n/g, "\n");
+ JSSpec.DSL.Subject.prototype.should_be.call({ target: cleanedActualText }, expectedText);
+};
+
+JSSpec.addScriptReference = function(scriptUrl) {
+ if (window.console)
+ console.log("Loading " + scriptUrl + "...");
+ document.write("<scr" + "ipt type='text/javascript' src='" + scriptUrl + "'></sc" + "ript>");
+};
View
@@ -286,7 +286,7 @@ JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) {
JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) {
var examples = [];
for(name in entries) if(entries.hasOwnProperty(name)) {
- examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
+ examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach, this.context));
}
return examples;
};
@@ -331,12 +331,13 @@ JSSpec.Spec.prototype.getExecutor = function() {
/**
* Example
*/
-JSSpec.Example = function(name, target, before, after) {
+JSSpec.Example = function(name, target, before, after, context) {
this.id = JSSpec.Example.id++;
this.name = name;
this.target = target;
this.before = before;
this.after = after;
+ this.context = context;
};
JSSpec.Example.id = 0;
@@ -559,6 +560,14 @@ JSSpec.Logger.prototype.onRunnerEnd = function() {
var title2 = "Success";
}
this.blinkTitle(times,title1,title2);
+
+ var totalCount = JSSpec.runner.totalExamples,
+ failCount = JSSpec.runner.getTotalFailures(),
+ errorCount = JSSpec.runner.getTotalErrors(),
+ successCount = totalCount - failCount - errorCount,
+ durationSeconds = document.getElementById("total_elapsed").innerHTML,
+ message = durationSeconds + " seconds, " + totalCount + " examples (" + successCount + " succeeded, " + failCount + " failed, " + errorCount + " errored)";
+ console.log("Finished: " + message);
};
JSSpec.Logger.prototype.blinkTitle = function(times, title1, title2) {
@@ -631,6 +640,17 @@ JSSpec.Logger.prototype.onExampleEnd = function(example) {
document.getElementById("total_elapsed").innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000;
document.title = progress + "%: " + this._title;
+
+ // Also log to console, so we can pick this up in PhantomJS
+ var logResult = {
+ error: !!example.isError(),
+ failure: !!example.isFailure(),
+ ok: !(example.isError() || example.isFailure()),
+ context: example.context,
+ name: example.name,
+ exception: example.exception && { message: example.exception.message, file: example.exception.fileName, line: example.exception.lineNumber }
+ }
+ console.log("Result: " + JSON.stringify(logResult));
};
/**
View
@@ -13,41 +13,12 @@
<script type="text/javascript" src="lib/innershiv.js"></script>
<script type="text/javascript" src="lib/diff_match_patch.js"></script>
<script type="text/javascript" src="lib/JSSpec.js"></script>
+ <script type="text/javascript" src="lib/JSSpec.extensions.js"></script>
+ <script type="text/javascript" src="lib/json2.js"></script>
<script type="text/javascript">
- JSSpec.DSL.Subject.prototype.should_be_one_of = function (expectedPossibilities) {
- for (var i = 0; i < expectedPossibilities.length; i++) {
- var matcher = JSSpec.EqualityMatcher.createInstance(expectedPossibilities[i], this.target);
- if (matcher.matches())
- return;
- }
- JSSpec._assertionFailure = { message: "Should be one of the values: " + expectedPossibilities.toString() + "; was: " + this.target.toString() };
- throw JSSpec._assertionFailure;
- };
- JSSpec.DSL.Subject.prototype.should_contain = function (expected) {
- if (this.target.indexOf(expected) >= 0)
- return;
- JSSpec._assertionFailure = { message: "Should contain: " + expected.toString() + "; was: " + this.target.toString() };
- throw JSSpec._assertionFailure;
- };
- JSSpec.DSL.Subject.prototype.should_contain_html = function (expectedHtml) {
- var cleanedHtml = this.target.innerHTML.toLowerCase().replace(/\r\n/g, "");
- // IE < 9 strips whitespace immediately following comment nodes. Normalize by doing the same on all browsers.
- cleanedHtml = cleanedHtml.replace(/(<!--.*?-->)\s*/g, "$1");
- expectedHtml = expectedHtml.replace(/(<!--.*?-->)\s*/g, "$1");
- // Also remove __ko__ expando properties (for DOM data) - most browsers hide these anyway but IE < 9 includes them in innerHTML
- cleanedHtml = cleanedHtml.replace(/ __ko__\d+=\"(ko\d+|null)\"/g, "");
- JSSpec.DSL.Subject.prototype.should_be.call({ target: cleanedHtml }, expectedHtml);
- };
- JSSpec.DSL.Subject.prototype.should_contain_text = function (expectedText) {
- var actualText = 'textContent' in this.target ? this.target.textContent : this.target.innerText;
- var cleanedActualText = actualText.replace(/\r\n/g, "\n");
- JSSpec.DSL.Subject.prototype.should_be.call({ target: cleanedActualText }, expectedText);
- };
-
+ // An external runner, such as PhantomJS, can prepopulate window.koFilename (e.g., to run tests against the minified build)
+ JSSpec.addScriptReference(window.koFilename || "../build/knockout-raw.js");
</script>
- <script type="text/javascript" src="lib/json2.js"></script>
- <script type="text/javascript" src="../build/knockout-raw.js"></script>
-
<script type="text/javascript" src="memoizationBehaviors.js"></script>
<script type="text/javascript" src="subscribableBehaviors.js"></script>
<script type="text/javascript" src="observableBehaviors.js"></script>
View
@@ -0,0 +1,37 @@
+var fs = require('fs'),
+ system = require('system'),
+ page = require('webpage').create(),
+ specFilename = fs.absolute((system.args.length > 1 && system.args[1]) || 'spec/runner.html'),
+ specFailureLog = [];
+
+// Configure the JsSpec runner to test the minified KO build. Paths are relative to specFilename.
+page.onInitialized = function() {
+ page.evaluate(function() { window.koFilename = '../build/output/knockout-latest.js'; });
+};
+
+// Intercept JsSpec log messages and map them to PhantomJS output
+page.onConsoleMessage = function(msg) {
+ var isExampleCompleted = typeof msg === 'string' && /^Result\:/.test(msg),
+ isSuiteCompleted = typeof msg === 'string' && /^Finished\:/.test(msg);
+
+ if (isExampleCompleted) {
+ // Parse the JSON and log the example only if it failed
+ var exampleResult = JSON.parse(msg.substring(7));
+ if (!exampleResult.ok) {
+ console.log(msg);
+ specFailureLog.push(exampleResult);
+ }
+ } else if (isSuiteCompleted) {
+ console.log('JsSpec ' + msg);
+ phantom.exit(specFailureLog.length); // 0 means success
+ } else {
+ console.log(msg);
+ }
+};
+
+page.open('file://' + specFilename, function (status) {
+ if (status !== 'success') {
+ console.log('Error: Could not load the runner page at ' + specFilename);
+ phantom.exit(1);
+ }
+});
@@ -331,14 +331,20 @@ ko.bindingHandlers['html'] = {
}
};
+var classesWrittenByBindingKey = '__ko__cssValue';
ko.bindingHandlers['css'] = {
'update': function (element, valueAccessor) {
- var value = ko.utils.unwrapObservable(valueAccessor() || {});
- for (var className in value) {
- if (typeof className == "string") {
+ var value = ko.utils.unwrapObservable(valueAccessor());
+ if (typeof value == "object") {
+ for (var className in value) {
var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
}
+ } else {
+ value = String(value || ''); // Make sure we don't try to store or set a non-string value
+ ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
+ element[classesWrittenByBindingKey] = value;
+ ko.utils.toggleDomNodeCssClass(element, value, true);
}
}
};
@@ -10,7 +10,9 @@
case 'option':
if (element[hasDomDataExpandoProperty] === true)
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
- return element.getAttribute("value");
+ return ko.utils.ieVersion <= 7
+ ? (element.getAttributeNode('value').specified ? element.value : element.text)
+ : element.value;
case 'select':
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
default:
@@ -29,9 +29,8 @@ ko.templateRewriting = (function () {
// For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
// anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
// extra indirection.
- var applyBindingsToNextSiblingScript = "ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { \
- return (function() { return { " + rewrittenDataBindAttributeValue + " } })() \
- })";
+ var applyBindingsToNextSiblingScript =
+ "ko.__tr_ambtns(function(){return(function(){return{" + rewrittenDataBindAttributeValue + "} })()})";
return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
}
@@ -60,5 +59,6 @@ ko.templateRewriting = (function () {
}
})();
-ko.exportSymbol('templateRewriting', ko.templateRewriting);
-ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templateRewriting.applyMemoizedBindingsToNextSibling); // Exported only because it has to be referenced by string lookup from within rewritten template
+
+// Exported only because it has to be referenced by string lookup from within rewritten template
+ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling);
Oops, something went wrong.

0 comments on commit a3afdeb

Please sign in to comment.